Mobiele apps programmeren met Xamarin – Anatomie van een C#-programma – Events en asynchroon programmeren

print

Inhoud


Wat vooraf ging

De XAML-code is:

<?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:App4" x:Class="App4.MainPage">
    <StackLayout>
        <Button Text="OK"></Button>
        <Label x:Name="lblUitvoer" Text="Welcome to Xamarin Forms!" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" />
    </StackLayout>

</ContentPage>
  • De ContentPage bevat een StackLayout die dienst doet als een “container”. Alle andere elementen bevinden zich binnen deze StackLayout.
  • Eerst wordt een Button toegevoegd, vervolgens een Label.
  • Merk de x: voor de Name op! Die staat er omdat het attribuut Name uit de namespace “xmlns:x=”http://schemas.microsoft.com/winfx/2009/xaml” komt die een x als prefix heeft.
  • De Label wordt verticaal gecentreerd over de beschikbare ruimte door de LayoutOption VerticalOptions="CenterAndExpand".

Events

Events zijn acties die de gebruiker kan doen met een object, of dat een object zelf kan doen.

Ik herneem hier even het stukje rond event-driven dat ik al eens eerder geschreven heb.

Events/gebeurtenissen zijn acties die kunnen gebeuren of uitgevoerd worden tijdens het werken met het programma. Voorbeelden van events zijn:

  • de pagina wordt geladen
  • de pagina is geladen
  • de gebruiker klikt op een knop
  • de gebruiker beweegt de muis over een object
  • de gebruiker typt een toets in

Bij event-driven programmeren wordt de control flow, de opeenvolging van de uit te voeren instructies, van het programma bepaald door de volgorde waarin de events/gebeurtenissen plaatsvinden. De instructies die horen bij het klikken op een specifieke knop worden pas uitgevoerd indien er daadwerkelijk op die specifieke knop gedrukt wordt.

Programma’s die veel interactie vragen van de gebruiker zoals interactieve websites, mobiele apps, eenvoudige spelletjes zijn vaak event-driven. Deze programma’s beschikken dan ook vaak over een geschikte gebruikersvriendelijke GUI (Grafische User Interface).

Bij event-driven ontwerpen bouwt u (vaak) eerst de GUI en bedenkt u welke events er kunnen plaatsvinden of nodig zijn (let op, de GUI wordt gebouwd met de nodige events in gedachte, zo zal u een knop toevoegen aan de GUI juist me de bedoeling om er op te kunnen klikken en niet andersom. U gaat geen knop plaatsen omdat die er mooi staat en u vervolgens de vraag stellen wat er zou kunnen gebeuren als u op deze knop zou drukken). Vervolgens programmeert u elk van deze events.

Voorbeeld

Op een knop kan de gebruiker klikken. De XAML-code is te vergelijken met het instellen van een eigenschap.

<Button Text="OK" Clicked="Button_Clicked" />

Er is echter een heel belangrijk verschil! In de code-behind is een Event handler aangemaakt waar u de code kunt programmeren die moet uitgevoerd worden als u op de knop klikt.

using System;
using Xamarin.Forms;

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

        private void Button_Clicked(object sender, EventArgs e)
        {

        }
    }
}

Merk ook op dat er een nieuwe Namespace is toegevoegd, nodig voor de event handler:

using System;

Een event is private (dus niet beschikbaar buiten deze klasse, wat logisch is want deze knop maakt enkel deel uit van deze klasse) en geeft een void terug (dus geeft geen berekende waarde terug, wat eveneens logisch is want het klikken op een knop zal wel een aantal instructies uitvoeren maar op zich “berekening” maken die een waarde teruggeeft).

Een event kent ook twee parameters/argumenten:

  1. object sender: om te bepalen welk object deze event “getriggerd” heeft.
  2. EventArgs e: eventuele argumenten die eigen zijn aan het type van de event.

Pas de code aan zodat een klik op de knop de tekst “Hello, world!” in de Label lblUitvoer getoond wordt.

using System;
using Xamarin.Forms;

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

        private void Button_Clicked(object sender, EventArgs e)
        {
            lblUitvoer.Text = "Hello, world!";
        }
    }
}

IntelliSense

Gebruik ook hier IntelliSense om de Event Handler door Visual Studio te laten aanmaken. Type eerst de methode Clicked, typ vervolgens het =-teken en kies voor het aanmaken van de <New Event handler>. Visual Studio maakt dan ook automatisch de code-behind aan.


Asynchroon programmeren

In plaats van de tekst in een Label te tonen wenst u de boodschap in een boodschappenvenster weer te geven.

Om een boodschappenvenster weer te geven gebruikt u een DisplayAlert(). De syntax is:

public Task DisplayAlert (String title, String message, String cancel)

Merk op dat er een Task wordt teruggegeven. Een taak neemt tijd in beslag en kan het programma “ophouden”.

DisplayAlert verwacht 3 parameters, 3 teksten, respectievelijk:

  • de titel
  • de boodschap
  • de tekst op de Cancel-knop.

Een voorbeeld van een DisplayAlert kan zijn:

DisplayAlert("Alert", "Hello, world!", "OK");

De volledige code is nu:

using System;
using Xamarin.Forms;

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

        private void Button_Clicked(object sender, EventArgs e)
        {
            DisplayAlert("Alert", "Hello, world!", "OK");
        }
    }
}

Deze code werkt maar…

Met bovenstaande code wordt het boodschappenvenster getoond maar wordt ook gewacht tot de gebruiker het boodschappenvenster beëindigt. Dit betekent dat moest uw applicatie ook een andere taak aan het uitvoeren zijn (bv. een download van een bestand) deze taak onderbroken wordt tot de gebruiker het boodschappenvenster afsluit. Het vertraagt dus het programma.

De oplossing ligt in asynchroon programmeren (wie meer wilt weten moet zeker de vorige link eens lezen en ook deze link, ik ga voorlopig niet echt in detail gaan maar de praktische uitvoering demonstreren).

You can avoid performance bottlenecks and enhance the overall responsiveness of your application by using asynchronous programming. Asynchrony is essential for activities that are potentially blocking, such as web access. Access to a web resource sometimes is slow or delayed. If such an activity is blocked in a synchronous process, the entire application must wait. In an asynchronous process, the application can continue with other work that doesn’t depend on the web resource until the potentially blocking task finishes.

DisplayAlert() is een methode die een taak uitvoert. Een taak representeert een asynchrone operatie. Concreet betekent dit dat, terwijl er gewacht wordt tot de taak beëindigd is, de applicatie kan verder werken aan andere lopende taken. Dit betekent dus dat het programma niet vertraagt.

Dit gebeurt in twee stappen.

Maak de functie asynchroon door toevoeging van async.

private async void Button_Clicked(object sender, EventArgs e)

Dit genereert onderstaande melding.

Dit brengt ons naar de tweede stap, het toevoegen van await voor de taak.

await DisplayAlert("Alert", "Hello, world!", "OK");

An async method runs synchronously until it reaches its first await expression, at which point the method is suspended until the awaited task is complete. In the meantime, control returns to the caller of the method.

The await operator is applied to a task in an asynchronous method to insert a suspension point in the execution of the method until the awaited task completes. The task represents ongoing work. await can only be used in an asynchronous method modified by the async keyword. Such a method, defined by using the async modifier and usually containing one or more await expressions, is referred to as an async method.

De aangepaste code is nu:

using System;
using Xamarin.Forms;

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

        private async void Button_Clicked(object sender, EventArgs e)
        {
            await DisplayAlert("Alert", "Hello, world!", "OK");
        }
    }
}

Er is niets veranderd aan de eigenlijke uitvoer, maar er wordt nu wel veel efficiënter gewerkt (al maakt dit voor deze eenvoudige app weinig, om niet te zeggen geen, verschil uit).


Event parameters – object sender

object sender kan gebruikt worden om te bepalen welk object de event aanroept (“triggert”).

Stel, u hebt een pagina met 3 knoppen. Het kan er als volgt uitzien:

De XAML-code:

<?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:App4" x:Class="App4.MainPage">
    <StackLayout>
        <Button Text="Boven" Clicked="Button_Clicked" VerticalOptions="Start"></Button>
        <Button Text="Midden" Clicked="Button_Clicked" VerticalOptions="CenterAndExpand"></Button>
        <Button Text="Onder" Clicked="Button_Clicked" VerticalOptions="End"></Button>
    </StackLayout>

</ContentPage>

Ik had kunnen kiezen voor 3 verschillende event-handlers maar ik heb dit bewust niet gedaan (Ik pas het DRY-principe toe (Don’t Repeat Yourself) want 3 afzonderlijke events zouden leiden tot 3 keer dezelfde code die moet worden uitgevoerd). Alle drie de knoppen verwijzen naar dezelfde event Clicked="Button_Clicked". U kunt hiertoe ook opnieuw gebruik maken van IntelliSense:

Ik gebruik opnieuw LayoutOptions om de verticale positie van de knoppen te bepalen, respectievelijk:

  • BovenVerticalOptions="Start"
  • MiddenVerticalOptions="CenterAndExpand"
  • OnderVerticalOptions="End"

De code-behind wordt nu:

using System;
using Xamarin.Forms;

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

        private async void Button_Clicked(object sender, EventArgs e)
        {
            Button Knop = (Button)sender;
            await DisplayAlert("Alert", "U klikte op " + Knop.Text + ".", "OK");
        }
    }
}

U moet achterhalen welke knop aangeklikt is. U gebruikt hiervoor het argument sender. De sender wordt geconverteerd naar het type Button en bewaart in een variabele Knop van het type Button.

Button Knop = (Button)sender;

Vervolgens wordt deze variabele Knop gebruikt om de tekst van de aangeklikte knop op te vragen en weer te geven in een boodschappenvenster.

await DisplayAlert("Alert", "U klikte op " + Knop.Text + ".", "OK");


Event parameters – EventArgs e

We maken een nieuwe app aan of we passen de pagina aan zodat we het onderstaande krijgen:

<?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:App4" x:Class="App4.MainPage">
    <StackLayout>
        <Slider Maximum="100" Minimum="0" ValueChanged="Slider_ValueChanged" VerticalOptions="CenterAndExpand"></Slider>
        <Label x:Name="lblUitvoer" VerticalOptions="CenterAndExpand" HorizontalTextAlignment="Center"></Label>
    </StackLayout>

</ContentPage>
  • De pagina bevat een Slider en een Label.
  • De tekst in het Label moet horizontaal gecentreerd worden via HorizontalTextAlignment="Center".
  • Als de waarde van de Slider verandert ValueChanged="Slider_ValueChanged" dan moet een tekst met de oude en de nieuwe waarde worden weergegeven.

Om de aangemaakte event in de code-behind te bekijken kunt u rechts klikken op de event (in XAML) en vervolgens kiezen voor Go To Definiton.

Dit is de aangemaakte event in code-behind:

private void Slider_ValueChanged(object sender, ValueChangedEventArgs e)
{
}

Een event is private (dus niet beschikbaar buiten deze klasse, wat logisch is want deze knop maakt enkel deel uit van deze klasse) en geeft een void terug (dus geeft geen berekende waarde terug, wat eveneens logisch is want het klikken op een knop zal wel een aantal instructies uitvoeren maar op zich “berekening” maken die een waarde teruggeeft).

Kijk nu ook even naar het tweede argument, deze is van het type ValueChangedEventArgs. De ValueChangedEventArgs heeft 2 eigenschappen (properties) OldValue en NewValue. Deze zijn binnen de event bereikbaar via de parameter e.

Om de weer te geven tekst te bouwen heb ik deze keer gebruik gemaakt van string.format().

lblUitvoer.Text = String.Format("U sleepte van {0} naar {1}.", e.OldValue, e.NewValue);

{0} en {1} zijn “placeholders” waar respectievelijk e.OldValue en e.NewValue worden aan toegekend.

U kunt deze “placeholders” nog verder opmaken.

Onderstaande code zal telkens exact 2 cijfers na de komma weergeven door toevoeging van :F2:

lblUitvoer.Text = String.Format("U sleepte van {0:F2} naar {1:F2}.", e.OldValue, e.NewValue);

De volledige code is nu:

using System;
using Xamarin.Forms;

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

        }

        private void Slider_ValueChanged(object sender, ValueChangedEventArgs e)
        {
            lblUitvoer.Text = String.Format("U sleepte van {0:F2} naar {1:F2}.", e.OldValue, e.NewValue);
        }
    }
}

Opdracht

Kunt u bovenstaande code aanpassen zodat de Slider standaard in het midden staat (waarde = 50) bij het starten van de app?


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

  • IC BC017 – kan ICT veilig en duurzaam gebruiken
  • IC BC234 – kan de basisprincipes van programmeren in een specifieke ontwikkelomgeving toepassen
  • IC BC236 – kan eenvoudige wijzigingen aan een programma aanbrengen
  • IC BC241 – kan een programma in een specifieke ontwikkelomgeving maken

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.