Mobiele apps programmeren met Xamarin – XAML binding

print

Inhoud


Wat vooraf ging

  • U hebt reeds kennis van XAML.

Wat is Binding?

Binding is het verbinden van eigenschappen van 2 objecten binnen XAML zodat een wijziging van de eigenschap in het ene object (Source) automatisch een wijziging van de eigenschap in het andere object (Target) teweeg brengt (en dit zonder verdere programmeercode via code-behind).

  • De eigenschap die gewijzigd wordt dient een BindableProperty te zijn.
  • De wijziging gebeurt automatisch, dus zonder verdere programmeercode.

De Binding gebeurt in twee stappen:

  1. Zet de eigenschap BindingContext van het target-element gelijk aan de source.
  2. Verbindt de eigenschap van het target-element, de eigenschap die moet wijzigen, met de eigenschap van het source-element, die de waarde van de wijziging aanlevert via een Binding.

We komen hier zo dadelijk op terug.

Voorbeeld

De invoer in een tekstvak direct weergeven in een label.

De XAML bevat een StackLayout met een Entry en een Label. Niets speciaals. De XAML kan er als volgt uitzien.

<?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:BindingTest1" x:Class="BindingTest1.MainPage">
    <StackLayout Margin="10">
        <Entry x:Name="Invoer"></Entry>
        <Label x:Name="Uitvoer" />
    </StackLayout>

</ContentPage>

Zonder Binding

Laten we eerst eens bekijken hoe we dit zouden opvangen zonder Binding, dus op basis van events en code-behind.

  • Voeg de TextChanged-event toe.
<?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:BindingTest1" x:Class="BindingTest1.MainPage">
    <StackLayout Margin="10">
        <Entry x:Name="Invoer" TextChanged="Invoer_TextChanged"></Entry>
        <Label x:Name="Uitvoer" />
    </StackLayout>

</ContentPage>
  • Voeg aan de code-behind onderstaande code toe.
using Xamarin.Forms;

namespace BindingTest1
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void Invoer_TextChanged(object sender, TextChangedEventArgs e)
        {
            Uitvoer.Text = Invoer.Text;
        }
    }
}

Ok, dit werkt perfect en op zich is hier niets mis mee. Maar laten we dit nu eens herwerken met enkel XAML via Binding en dus zonder code-behind.

  • Verwijder de TextChanged-event zowel in XAML als in code-behind.

Met Binding

Weet dat Binding binnen XAML altijd tussen {} gebeurt.

Zoals we reeds gezien hebben gebeurt de Binding in 2 stappen:

De Binding gebeurt in twee stappen:

  • Zet de eigenschap BindingContext van het target-element gelijk aan de source. Dit gebeurt via:

BindingContext="{x:Reference Name=Invoer}"

De eigenschap BindingContext krijgt dus een referentie naar het object waarmee het gekoppeld wordt op basis van de naam van dit object.

De code kan nog iets compacter als volgt:

BindingContext="{x:Reference Invoer}"

  • Verbindt de eigenschap van het target-element, de eigenschap die moet wijzigen, met de eigenschap van het source-element, die de waarde van de wijziging aanlevert via een Binding. Dit gebeurt als volgt:

Text="{Binding Text}"

  • Wijzig de XAML in:
<?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:BindingTest1" x:Class="BindingTest2.MainPage">
    <StackLayout Margin="10">
        <Entry x:Name="Invoer" ></Entry>
        <Label x:Name="Uitvoer" BindingContext="{x:Reference Name=Invoer}" Text="{Binding Text}" />
    </StackLayout>

</ContentPage>

Voorbeeld

Gebruik een switch om een knop al dan niet beschikbaar te maken.

  • Start een nieuw Xamarin-project en geef het een passende naam (bv. BindingTest2). Of, pas het bovenstaande voorbeeld aan.
  • Pas de XAML-code aan zoals hieronder.
<?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:BindingTest2" x:Class="BindingTest2.MainPage">

    <StackLayout Margin="10">
        <Switch x:Name="AanUit" IsToggled="False" ></Switch>
        <Button BindingContext="{x:Reference Name=AanUit}" IsEnabled="{Binding IsToggled}" Text="OK"/>
    </StackLayout>

</ContentPage>

Binnen een StackLayout voegt u een Switch en een Button toe. De beschikbaarheid van de Button wordt bepaald door de Switch. Hiertoe moet het al dan niet “aan” staan van de Switch nagaan. Dit gebeurt via de eigenschap IsToggled. Het al dan niet beschikbaar zijn van de Button wordt bepaald door de eigenschap IsEnabled. De eigenschap IsEnabled van de Button moet dus verbonden worden met de eigenschap IsToggled van de Switch.

<Button BindingContext="{x:Reference Name=AanUit}" IsEnabled="{Binding IsToggled}" Text="OK"/>

De Switch is bij het opstarten “uit” door het instellen van IsToggled="False".

Voorbeeld

Gebruik een switch om een knop al dan niet beschikbaar te maken.

  • Start een nieuw Xamarin-project en geef het een passende naam (bv. BindingTest3). Of, pas het bovenstaande voorbeeld aan.
  • Pas de XAML-code aan zoals hieronder.
<?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:BindingTest3" x:Class="BindingTest3.MainPage">
    <StackLayout Margin="10">
        <Slider x:Name="slider" Minimum="0" Maximum="360"></Slider>
        <Label BindingContext="{x:Reference slider}" Text="{Binding Value, StringFormat='{0:N0}°'}" HorizontalOptions="Center"></Label>
        <Label BindingContext="{x:Reference slider}" Rotation="{Binding Value}" Text="PCVO Dender en Schelde" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" />
    </StackLayout>

</ContentPage>

Er wordt een Slider en twee Labels toegevoegd. De Slider wordt gebruikt om de Label te roteren en moet lopen van 0 tot 360 Minimum="0" Maximum="360".

Een eerste Label geeft het aantal graden weer en wordt daartoe verbonden met de Slider. Merk ook de specifieke weergave op StringFormat='{0:N0}°'. De eerste 0 is een placeholder waar de value zal in weergegeven worden. Deze value wordt opgemaakt via StringFormat als een numeriek getal met 0 decimale cijfers {0:N0}°. Tenslotte worden er graden ° aan toegevoegd. Merk op dat dit allemaal tussen enkele quotes staat (omdat er reeds dubbele quotes rond staan).

<Label BindingContext="{x:Reference slider}" Text="{Binding Value, StringFormat='{0:N0}°'}" HorizontalOptions="Center"></Label>

De eigenlijke Label die moet geroteerd worden wordt aan dezelfde Slider verbonden maar dan via de eigenschap Rotation="{Binding Value}".

<Label BindingContext="{x:Reference slider}" Rotation="{Binding Value}" Text="PCVO Dender en Schelde" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" />

Verschillende wegen

Gebruik meerdere sliders om een label in verschillende richtingen te kunnen roteren en te kunnen schalen.

Het probleem

Het probleem is dat er meerdere eigenschappen van het label moeten worden gewijzigd, namelijk Rotation, RotationX, RotationY en Scale. Deze 4 eigenschappen worden gewijzigd gebruikmakend van 4 verschillende Sliders.

En wat hebben we nu gezien?

Dat een element via BindingContext slechts met één ander element kan verbonden worden.

De oplossing

De oplossing is de richting/Mode van de Binding wijzigen.

De eigenschap Mode wordt gebruikt om de richting waarin de wijzig plaatsvindt te bepalen:

  • OneWay – veranderingen van de source naar de target. Dit is de standaard waarde en mag weggelaten worden.
  • TwoWay – verandering in beide richtingen zodat de source en de target altijd synchroon.
  • OneWayToSource – verandering van de target naar de source, deze optie kan gebruikt worden indien eenzelfde element meerdere bindingen moet hebben. De Binding wordt dan ingesteld bij de target.

Laten we eerst eens de volledige XAML bekijken.

<?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:BindingTest4" x:Class="BindingTest4.MainPage">

    <StackLayout Margin="10">
        <StackLayout Orientation="Horizontal">
            <Slider x:Name="sliderRotatie" BindingContext="{x:Reference label}" Value="{Binding Rotation, Mode=OneWayToSource}" Minimum="0" Maximum="360" HorizontalOptions="FillAndExpand"></Slider>
            <Label BindingContext="{x:Reference sliderRotatie}" Text="{Binding Value, StringFormat='Rotatie: {0:N0}°'}" HorizontalOptions="CenterAndExpand"></Label>
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Slider x:Name="sliderRotatieX" BindingContext="{x:Reference label}" Value="{Binding RotationX, Mode=OneWayToSource}" Minimum="0" Maximum="360" HorizontalOptions="FillAndExpand"></Slider>
            <Label BindingContext="{x:Reference sliderRotatieX}" Text="{Binding Value, StringFormat='RotatieX: {0:N0}°'}" HorizontalOptions="CenterAndExpand"></Label>
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Slider x:Name="sliderRotatieY" BindingContext="{x:Reference label}" Value="{Binding RotationY, Mode=OneWayToSource}" Minimum="0" Maximum="360" HorizontalOptions="FillAndExpand"></Slider>
            <Label BindingContext="{x:Reference sliderRotatieY}" Text="{Binding Value, StringFormat='RotatieY: {0:N0}°'}" HorizontalOptions="CenterAndExpand"></Label>
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Slider x:Name="sliderScale" BindingContext="{x:Reference label}" Value="{Binding Scale, Mode=OneWayToSource}" Maximum="10" Minimum="1" HorizontalOptions="FillAndExpand"></Slider>
            <Label BindingContext="{x:Reference sliderScale}" Text="{Binding Value, StringFormat='Schaal: {0:N0}'}" HorizontalOptions="CenterAndExpand"></Label>
        </StackLayout>

        <Label x:Name="label" Text="PCVO Dender en Schelde" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" />
    </StackLayout>

</ContentPage>

Merk eerst op dat de verschillende StackLayout geen perfecte layout mogelijk maken. Ik had hier beter gekozen voor een Grid, maar omdat we dit op dit ogenblik van de cursus nog niet gezien hebben opteerde ik toch voor de StackLayout.

Als we kijken de Label met de tekst “PCVO Dender en Scheld”, die eigenlijk moet “gemanipuleerd” worden, de source dus, dan merken we op dat deze geen bindingeigenschappen heeft.

<Label x:Name="label" Text="PCVO Dender en Schelde" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" />

De bindingeigenschappen bevinden zich bij de verschillende Sliders. De binding naar de label gebeurt via Mode=OneWayToSource. Dit maakt het mogelijk meerdere Sliders te verbinden met eenzelfde source BindingContext="{x:Reference label}" .

<Slider x:Name="sliderRotatie" BindingContext="{x:Reference label}" Value="{Binding Rotation, Mode=OneWayToSource}" Minimum="0" Maximum="360" HorizontalOptions="FillAndExpand"></Slider>

De Labels die naast de Sliders staan hebben een gewone, standaard verbinding met deze Sliders.

<Label BindingContext="{x:Reference sliderRotatie}" Text="{Binding Value, StringFormat='Rotatie: {0:N0}°'}" HorizontalOptions="CenterAndExpand"></Label>

Alle bindings verlopen verder identiek, alleen zal een andere eigenschap van de source verbonden worden.


Binding via C#

De doorzichtbaarheid (opacity) van een label wordt bepaald door een slider.

Nu we in een vorige post gezien hebben dat we alles wat we via XAML ontwerpen ook kunnen programmeren in C# staan we even stil hoe we Binding programmeren in C#.

  • Start een nieuw Xamarin-project en geef het een passende naam (bv. BindingTestC).
  • Klik rechts op het project BindingTestC (Portable) en klik op Add – New item….
  • Selecteer het item Form Blank ContentPage en geef het een geschikte naam, bv. Doorzichtbaar.cs.
  • Vergeet niet deze pagina als MainPage in te stellen in App.xaml.cs.

U krijgt onderstaande C#-code:

using Xamarin.Forms;

namespace BindingTestC
{
    public class Doorzichtbaar : ContentPage
    {
        public Doorzichtbaar()
        {
            Content = new StackLayout
            {
                Children = {
                    new Label { Text = "Hello Page" }
                }
            };
        }
    }
}

De doorzichtbaarheid (OpacityProperty) van een Label wordt bepaald door een Slider. Deze twee elementen moeten dan ook aan mekaar verbonden worden.

  • Eerst voegen we een Label en een Slider binnen een StackLayout. De code zou moeten duidelijk zijn (al is ze iets anders opgesteld dan in de vorige post).
using Xamarin.Forms;

namespace BindingTestC
{
    public class Doorzichtbaar : ContentPage
    {
        public Doorzichtbaar()
        {
            Label label = new Label
            {
                Text = "PCVO Dender en Schelde",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.Center
            };

            Slider slider = new Slider
            {
                VerticalOptions = LayoutOptions.CenterAndExpand
            };

            Padding = new Thickness(10, 0);
            Content = new StackLayout
            {
                Children = { label, slider }
            };

        }
    }
}

De binding gebeurt nu in 2 stappen.

  • Stel de BindingContext property van de target (hier de Label ) in om te refereren naar het source (hier de Slider): label.BindingContext = slider;
  • Roep de functie SetBinding aan op de target en stel de eigenschappen in die moeten gewijzigd worden: label.SetBinding(Label.OpacityProperty, "Value");.
using Xamarin.Forms;

namespace BindingTestC
{
    public class Doorzichtbaar : ContentPage
    {
        public Doorzichtbaar()
        {
            Label label = new Label
            {
                Text = "PCVO Dender en Schelde",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.Center
            };

            Slider slider = new Slider
            {
                VerticalOptions = LayoutOptions.CenterAndExpand
            };

            //Zet de binding context: target is Label; source is Slider.
            label.BindingContext = slider;

            // Verbindt de properties: target is Opacity; source is Value.
            label.SetBinding(Label.OpacityProperty, "Value");

            Padding = new Thickness(10, 0);
            Content = new StackLayout
            {
                Children = { label, slider }
            };

        }
    }
}

De code kan eventueel compacter via de Binding-klasse.

Binding binding = new Binding("Value", source: slider);
label.SetBinding(Label.OpacityProperty, binding);


Praktisch voorbeeld: een eigen webbrowser

Maak een eigen webbrowser.

Met Binding is het heel eenvoudig om een eigen webbrowser aan te maken.

Xamarin komt met een WebView-element.

Een WebView heeft volgende eigenschappen:

  • Source – de url van de website.
  • CanGoBack – een boolen (waar of niet waar) die aangeeft of er kan teruggegaan worden naar een vorige webpagina.
  • CanGoForward – een boolen (waar of niet waar) die aangeeft of er kan gegaan worden naar een volgende webpagina.

Een WebView heeft volgende methoden:

  • GoBack – indien de eigenschap CanGoBack waar is.
  • GoForward – indien de eigenschap CanGoForward waar is.

De eigenschappen CanGoBack en CanGoForward worden verbonden met de IsEnabled-eigenschap van 2 Buttons.

XAML

  • Pas de XAML van MainPage.xaml aan zoals hieronder:
<?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:MijnWebbrowser" x:Class="MijnWebbrowser.MainPage">

    <StackLayout Margin="10">
        <Entry Keyboard="Url" Placeholder="Url" Completed="OnEntryCompleted" />

        <StackLayout Orientation="Horizontal" BindingContext="{x:Reference webView}">

            <Button Text="&#x21D0;" FontSize="Large" IsEnabled="{Binding CanGoBack}" Clicked="OnGoBackClicked" HorizontalOptions="FillAndExpand"/>

            <Button Text="&#x21D2;" FontSize="Large" IsEnabled="{Binding CanGoForward}" Clicked="OnGoForwardClicked" HorizontalOptions="FillAndExpand"/>
        </StackLayout>

        <WebView x:Name="webView" Source="https://pcvodenderenschelde.be" VerticalOptions="FillAndExpand" />
    </StackLayout>

</ContentPage>

Merk op dat er ook reeds de nodige events zijn toegevoegd en dat de BindingContext="{x:Reference webView}" toegekend is aan de StackLayout en wordt overgeërfd door de beide Buttons. De eigenlijke binding gebeurt dan via IsEnabled="{Binding CanGoBack}" en IsEnabled="{Binding CanGoForward}" van de respectievelijke Buttons, die ook de wat eigenaarde tekst meekrijgen Text="⇐" en Text="⇒" om de “pijltjes” weer te geven.

Code-behind

Doordat er events zijn toegevoegd kunt u al raden dat er ook enige code-behind nodig is.

using System;
using Xamarin.Forms;

namespace MijnWebbrowser
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void OnEntryCompleted(object sender, EventArgs e)
        {
            webView.Source = ((Entry)sender).Text;
        }

        private void OnGoBackClicked(object sender, EventArgs e)
        {
            webView.GoBack();
        }

        private void OnGoForwardClicked(object sender, EventArgs e)
        {
            webView.GoForward();
        }
    }
}

De ingetypte tekst van de Entry wordt toegekend aan de WebView.

webView.Source = ((Entry)sender).Text;

Uiteindelijk moet er ook vooruit

webView.GoForward();

en achteruit

webView.GoBack();

kunnen genavigeerd worden.

Zo, meer is er niet nodig voor een eenvoudige, eigen, webbrowser.


Behandelde Basiscompetenties uit de module ICT Programmeren – Specifieke ontwikkelomgeving: complexe functionaliteiten

  • IC BC235 – kan gevorderde principes van programmeren in een specifieke ontwikkelomgeving toepassen
  • IC BC246 – kan complexe ontwerpen in een specifieke ontwikkelomgeving maken
  • IC BC251 – kan een ontwerp in een specifieke ontwikkelomgeving verfijnen
  • IC BC253 – kan broncode in een specifieke ontwikkelomgeving optimaliseren
  • IC BC257 – heeft aandacht voor de gebruiksvriendelijkheid van de toepassing
  • IC BC288 – kan ICT-problemen oplossen

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.