Mobiele apps programmeren met Xamarin – De lay-out dynamisch bouwen met C#

print

Inhoud


Wat vooraf ging

U hebt tot nu toe, in de module “Eenvoudige functionaliteiten” alle formulieren aangemaakt in XAML. Niets mis mee, het dient ervoor.

Maar…

  • Soms kan het gebeuren dat bepaalde instructies niet kunnen via XAML of gewoonweg eenvoudiger kunnen via code in C#.
  • Wanneer u een pagina dynamisch, “At run-time”, wilt ontwerpen is het al zeker aan te raden die via C# te doen.

In deze handleiding gaan we dan ook zien hoe u een lay-out dynamisch kunt bouwen in C#.


Een dynamisch logboek aanmaken

We gaan een eenvoudig Logboek aanmaken, bestaande uit een tekstvak (Entry) en een knop (Button). Bij iedere klik op de knop wordt de datum en tijd en de ingevoerde tekst in de Entry toegevoegd aan de pagina.

 

Een niet-XAML ContentPage toevoegen

  • Klik rechts op het project Logboek (Portable) en klik op Add – New item….

  • Selecteer het item Form Blank ContentPage en geef het een geschikte naam, bv. LogboekPage.cs.

U ziet nu onderstaande code staan.

using Xamarin.Forms;

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

Merk op dat aan de Content van de pagina een StackLayout wordt toegevoegd.

Content = new StackLayout

Deze StackLayout krijgt dan Children in dit geval een Label met eigenschap Text = "Hello Page".

Children = {
new Label { Text = "Hello Page" }

We gaan nu de code als volgt wijzigen:

using Xamarin.Forms;

namespace Logboek
{
    public class LogboekPage : ContentPage
    {
        public LogboekPage()
        {
            this.Padding = new Thickness(10);

            Label Titel = new Label
            {
                Text = "LOGBOEK",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            };

            Content = new StackLayout
            {
                Children = {
                    Titel
                }
            };
        }
    }
}

Titel toevoegen

Eerst wordt er wat ruimte aan de binnenrand (Padding) gecreëerd, hier 10 punten langs alle 4 de zijden. Padding is van het type Thickness.

this.Padding = new Thickness(10);

Vervolgens wordt een nieuw Label aangemaakt met de titel LOGBOEK, gecentreerd en in een groot lettertype.

Label Titel = new Label
{
Text = "LOGBOEK",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
HorizontalOptions = LayoutOptions.CenterAndExpand
};

Merk vooral de manier op waarop de het grotere lettertype wordt ingesteld, door het opvragen aan de Device van de specifieke grootte via GetNamedSize. Merk ook de komma tussen de ingestelde eigenschappen op.

FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),

Lees meer over Fonts.

Tenslotte kennen we net aangemaakte Label toe als een Child binnen een StackLayout.

Content = new StackLayout
{
Children = {
Titel
}
};

Knop toevoegen

Voeg nu op een gelijkaardige manier een Button toe.

Button Knop = new Button
{
Text = "Toevoegen aan het logboek."
};

Let wel op, we moeten op deze knop kunnen klikken. Er moet dus ook een Click-event worden toegevoegd. Dit gebeurt als volgt.

Knop.Clicked += Knop_Clicked;

Maak vervolgens gebruik van IntelliSense om de eigenlijke event aan te maken.

De aangemaakte code is:

private void Knop_Clicked(object sender, EventArgs e)
{
throw new NotImplementedException();
}

We gaan deze straks verder uitwerken.

De volledige code tot nu toe is:

using System;
using Xamarin.Forms;

namespace Logboek
{
    public class LogboekPage : ContentPage
    {
        public LogboekPage()
        {
            this.Padding = new Thickness(10);

            Label Titel = new Label
            {
                Text = "LOGBOEK",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            };

            Button Knop = new Button
            {
                Text = "Toevoegen aan het logboek."
            };
            Knop.Clicked += Knop_Clicked;

            Content = new StackLayout
            {
                Children = {
                    Titel,
                    Knop
                }
            };

        }

        private void Knop_Clicked(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }
    }
}

De code voor de Knop_Clicked-event werken we zo dadelijk uit.

Een StackLayout binnen een ScrollView toevoegen

Nu wordt het ietsje moeilijker. Bij het klikken op de knop moet telkens een logbericht aangemaakt worden en toegevoegd. Waaraan wordt dit toegevoegd? Aan een StackLayout. Maar, omdat u wel eens heel veel logberichten zou kunnen toevoegen, meer dan er op het scherm passen moet er gescrold kunnen worden.

Dus, eerst maken we een nieuwe StackLayout aan.

StackLayout loggerLayout = new StackLayout();

Vervolgens maken we een ScrollView aan, die de beschikbare ruimte volledig vult en als content de net aangemaakte StackLayout krijgt.

ScrollView Scroll = new ScrollView
{
VerticalOptions = LayoutOptions.FillAndExpand,
Content = loggerLayout
};

Deze ScrollView wordt dan aan de “hoofd”-StackLayout toegekend.

De volledige code is nu:

using System;
using Xamarin.Forms;

namespace Logboek
{
    public class LogboekPage : ContentPage
    {
        public LogboekPage()
        {
            this.Padding = new Thickness(10);

            Label Titel = new Label
            {
                Text = "LOGBOEK",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            };

            Button Knop = new Button
            {
                Text = "Toevoegen aan het logboek."
            };
            Knop.Clicked += Knop_Clicked;

            StackLayout loggerLayout = new StackLayout();

            ScrollView Scroll = new ScrollView
            {
                VerticalOptions = LayoutOptions.FillAndExpand,
                Content = loggerLayout
            };

            Content = new StackLayout
            {
                Children = {
                    Titel,
                    Knop,
                    Scroll
                }
            };

        }

        private void Knop_Clicked(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }
    }
}

Het tekstvak toevoegen

Ik was het toch wel vergeten zeker, het tekstvak, de Entry, toe te voegen, boven de knop. Geen probleem, hier komt hij:

Entry Tekstvak = new Entry
{
Keyboard = Keyboard.Create(KeyboardFlags.Spellcheck | KeyboardFlags.Suggestions)
};

Merk ook op dat de spellingscontrole en de suggesties geactiveerd zijn door het instellen van de KeyboardFlags, iets dat binnen XAML niet zo eenvoudig zou zijn geweest (volgens de officiële uitleg kan het zelfs niet).

De volgorde van het aanmaken van de objecten is niet belangrijk, als ze maar in de juiste volgorde worden toegevoegd aan de StackLayout.

De volledige code is nu:

using System;
using Xamarin.Forms;

namespace Logboek
{
    public class LogboekPage : ContentPage
    {
        public LogboekPage()
        {
            this.Padding = new Thickness(10);

            Label Titel = new Label
            {
                Text = "LOGBOEK",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            };

            Button Knop = new Button
            {
                Text = "Toevoegen aan het logboek."
            };
            Knop.Clicked += Knop_Clicked;

            StackLayout loggerLayout = new StackLayout();

            ScrollView Scroll = new ScrollView
            {
                VerticalOptions = LayoutOptions.FillAndExpand,
                Content = loggerLayout
            };

            Entry Tekstvak = new Entry
            {
                Keyboard = Keyboard.Create(KeyboardFlags.Spellcheck | KeyboardFlags.Suggestions)
            };

            Content = new StackLayout
            {
                Children = {
                    Titel,
                    Tekstvak,
                    Knop,
                    Scroll
                }
            };

        }

        private void Knop_Clicked(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }
    }
}

De klik-Event

Bij het klikken op de knop moet een Label aan de StackLayout loggerLayout worden toegevoegd, samen met de datum en tijd. Het dynamisch kunnen aanmaken van deze labels was de hoofdreden om de pagina te bouwen via C# en niet via XAML.

De code kan er als volgt uitzien:

loggerLayout.Children.Add(
new Label
{
Text = DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss") + " - " + Tekstvak.Text,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
});

De volledige code:

using System;
using Xamarin.Forms;

namespace Logboek
{
    public class LogboekPage : ContentPage
    {
        public LogboekPage()
        {
            this.Padding = new Thickness(10);

            Label Titel = new Label
            {
                Text = "LOGBOEK",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            };

            Button Knop = new Button
            {
                Text = "Toevoegen aan het logboek."
            };
            Knop.Clicked += Knop_Clicked;

            StackLayout loggerLayout = new StackLayout();

            ScrollView Scroll = new ScrollView
            {
                VerticalOptions = LayoutOptions.FillAndExpand,
                Content = loggerLayout
            };

            Entry Tekstvak = new Entry
            {
                Keyboard = Keyboard.Create(KeyboardFlags.Spellcheck | KeyboardFlags.Suggestions)
            };

            Content = new StackLayout
            {
                Children = {
                    Titel,
                    Tekstvak,
                    Knop,
                    Scroll
                }
            };

        }

        private void Knop_Clicked(object sender, EventArgs e)
        {
            loggerLayout.Children.Add(
            new Label
            {
                Text = DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss") + "- " + Tekstvak.Text,
                FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
            });
        }
    }
}

Maar… nog voor u deze code laat lopen krijgt u foutmeldingen.

De reden is dat de objecten loggerLayout en Tekstvak niet gekend zijn.

Hoe komt dit?

Dit komt door scoping. Variabelen zijn alleen maar gekend binnen hun bereik bepaald door de {} waar ze toe behoren. Buiten deze {} zijn ze niet gekend en worden ze dus ook niet herkend. Beide objectenloggerLayout en Tekstvak zijn gedeclareerd in public LogboekPage() en zijn dus niet gekend binnen private void Knop_Clicked(object sender, EventArgs e)

De oplossing bestaat er nu uit deze objecten op een hoger niveau, dus binnen public class LogboekPage : ContentPage te declareren zodat ze bruikbaar worden binnen alle functies/methoden die onder deze, meer globale, functie/methode vallen.

Denk er ook aan dat u een variabele maar eenmaal kunt definiëren en dat u ze daarna gewoon moet gebruiken. Dit betekent concreet dat

Entry Tekstvak = new Entry
{
Keyboard = Keyboard.Create(KeyboardFlags.Spellcheck | KeyboardFlags.Suggestions)
};

Nu zal bestaan uit de declaratie op hoger niveau:

Entry Tekstvak = new Entry();

Waarna de variabele nadien, op een lager niveau, zijn waarden kan krijgen:

Tekstvak.Keyboard = Keyboard.Create(KeyboardFlags.Spellcheck | KeyboardFlags.Suggestions);

Nu zal bestaan uit een declaratie

De correcte, finale code is nu:

using System;
using Xamarin.Forms;

namespace Logboek
{
    public class LogboekPage : ContentPage
    {
        StackLayout loggerLayout = new StackLayout();
        Entry Tekstvak = new Entry();

        public LogboekPage()
        {
            this.Padding = new Thickness(10);

            Label Titel = new Label
            {
                Text = "LOGBOEK",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            };

            Button Knop = new Button
            {
                Text = "Toevoegen aan het logboek."
            };
            Knop.Clicked += Knop_Clicked;

            ScrollView Scroll = new ScrollView
            {
                VerticalOptions = LayoutOptions.FillAndExpand,
                Content = loggerLayout
            };

            Tekstvak.Keyboard = Keyboard.Create(KeyboardFlags.Spellcheck | KeyboardFlags.Suggestions);

            Content = new StackLayout
            {
                Children = {
                    Titel,
                    Tekstvak,
                    Knop,
                    Scroll
                }
            };

        }

        private void Knop_Clicked(object sender, EventArgs e)
        {
            loggerLayout.Children.Add(new Label
            {
                Text = DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss") + "- " + Tekstvak.Text,
                FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
            });
        }
    }
}
  • Vergeet niet deze pagina als MainPage in te stellen in App.xaml.cs.

MainPage = new LogboekPage();


Een Verwijderen-knop toevoegen

We gaan het bovenstaande Logboek wijzigen door een Verwijderen-knop toe te voegen die de oudste log verwijdert. Deze Verwijderen-knop mag enkel aanklikbaar zijn als er logberichten toegevoegd/beschikbaar zijn.

 

Laten we eerst de code al eens bekijken en dan de wijzigingen bespreken.

using System;
using Xamarin.Forms;

namespace Logboek
{
    public class LogboekPage : ContentPage
    {
        StackLayout loggerLayout = new StackLayout();
        Entry Tekstvak = new Entry();
        Button ToevoegenKnop, VerwijderenKnop;

        public LogboekPage()
        {
            this.Padding = new Thickness(10);

            Label Titel = new Label
            {
                Text = "LOGBOEK",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            };

            ToevoegenKnop = new Button
            {
                Text = "Toevoegen aan het logboek."
            };
            ToevoegenKnop.Clicked += Knop_Clicked;

            VerwijderenKnop = new Button
            {
                Text = "De oudste log verwijderen.",
                BackgroundColor = Color.Red,
                IsEnabled = false
            };
            VerwijderenKnop.Clicked += Knop_Clicked;


            ScrollView Scroll = new ScrollView
            {
                VerticalOptions = LayoutOptions.FillAndExpand,
                Content = loggerLayout
            };

            Tekstvak.Keyboard = Keyboard.Create(KeyboardFlags.Spellcheck | KeyboardFlags.Suggestions);

            Content = new StackLayout
            {
                Children = {
                    Titel,
                    Tekstvak,
                    ToevoegenKnop,
                    VerwijderenKnop,
                    Scroll
                }
            };

        }

        private void Knop_Clicked(object sender, EventArgs e)
        {
            Button Knop = (Button)sender;

            if(Knop == ToevoegenKnop){
                loggerLayout.Children.Add(new Label
                {
                    Text = DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss") + " - " + Tekstvak.Text,
                    FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
                });
            }
            else
            {
                loggerLayout.Children.RemoveAt(0);
            }

            VerwijderenKnop.IsEnabled = loggerLayout.Children.Count > 0;
        }
    }
}

We hebben dus niet 1 knop maar 2 knoppen nodig en omdat beide knoppen dezelfde event aanroepen (dit is geen verplichting maar een keuze die ik gemaakt heb), en er binnen de event gecontroleerd wordt welke knop er aangeklikt is, moeten beiden knoppen ook op het hoogste niveau public class LogboekPage : ContentPage worden gedeclareerd zodat ze zowel in public LogboekPage() als in private void Knop_Clicked(object sender, EventArgs e). Ik heb de naam van de bestaande knop gewijzigd in ToevoegenKnop en de tweede knop heb ik de naam VerwijderenKnop gegeven.

Button ToevoegenKnop, VerwijderenKnop;

Merk op dat dit een licht andere syntax is dan:

Entry Tekstvak = new Entry();

Hier wordt meteen een nieuwe Entry toegekend, bij de Buttons gaan we dit nog moeten doen.

De reeds bestaande knop is gewijzigd naar:

ToevoegenKnop = new Button
{
Text = "Toevoegen aan het logboek."
};
ToevoegenKnop.Clicked += Knop_Clicked;

De tweede knop krijgt een rode achtergrond BackgroundColor = Color.Red en is standaard disabled IsEnabled = false.

VerwijderenKnop = new Button
{
Text = "De oudste log verwijderen.",
BackgroundColor = Color.Red,
IsEnabled = false
};
VerwijderenKnop.Clicked += Knop_Clicked;

Merk op dat beide knoppen dezelfde Knop_Clicked-event aanroepen.

Beide knoppen moeten toegevoegd worden.

Content = new StackLayout
{
Children = {
Titel,
Tekstvak,
ToevoegenKnop,
VerwijderenKnop,
Scroll
}
};

De Knop_Clicked-event zal nu moeten nagaan welke knop aangeklikt is.

Button Knop = (Button)sender;
if(Knop == ToevoegenKnop){}

Bij he toevoegen wijzigt er niets aan de code.

Is de VerwijderenKnop aangeklikt dan moet de oudste log (of het eerste child) verwijderd worden via:

loggerLayout.Children.RemoveAt(0);

Als er nog children zijn blijft de VerwijderenKnop actief.

VerwijderenKnop.IsEnabled = loggerLayout.Children.Count > 0;

loggerLayout.Children.Count > 0 telt het aantal children en geeft de waarde true of false terug die dan toegekend wordt aan VerwijderenKnop.IsEnabled.


Opmaak herhalen via functies

Stel dat u bepaalde opmaak vaak moet herhalen binnen hetzelfde project (of in andere projecten) dan speelt ook hier, bij de opmaak, het DRY-principe (Don’t Repeat Yourself). U kunt dit eveneens bekomen door de code die zich herhaalt af te zonderen in een eigen functie.

Belangrijk is dat deze functie een view teruggeeft.

Onderstaande code bouwt een CreateLoginForm-functie. De code op zich zou geen geheimen meer mogen bevatten.

View CreateLoginForm()
{
    var usernameEntry = new Entry { Placeholder = "Username" };
    var passwordEntry = new Entry
    {
        Placeholder = "Password",
        IsPassword = true
    };

    return new StackLayout
    {
        Children = {
            usernameEntry,
            passwordEntry
        }
    };
}

Praktisch voorbeeld: Een dynamische tabel

Maak, dynamisch, een tabel aan met willekeurige getallen.

using System;
using Xamarin.Forms;

namespace OpmaakOpMaat
{
    public class Dynamischetabel : ContentPage
    {
        public Dynamischetabel()
        {
            Label Titel = new Label
            {
                Text = "Dynamische tabel",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            };

            StackLayout Hoofd = new StackLayout();
            Random Willekeurig = new Random();
            Hoofd.HorizontalOptions = LayoutOptions.Center;

            for (int x = 0; x < 4; x++)
            {
                StackLayout Stapel = new StackLayout();
                Stapel.Orientation = StackOrientation.Horizontal;

                for (int y = 0; y < 6; y++)
                {
                    Stapel.Children.Add(
                        new Label
                        {
                            Text = Willekeurig.Next(1, 45).ToString(),
                            FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
                            BackgroundColor = Color.Yellow,
                            HorizontalTextAlignment = TextAlignment.Center,
                            TextColor = Color.Red,
                            WidthRequest = 40
                        });
                }

                Hoofd.Children.Add(Stapel);
            }

            Padding = new Thickness(20);
            Content = new StackLayout
            {
                Children = {
                    Titel, Hoofd
                }
            };
        }
    }
}

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

  • IC BC235 – kan gevorderde principes van programmeren in een specifieke ontwikkelomgeving toepassen
  • IC BC238 – kan een complex programma wijzigen
  • 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

Eén reactie

  1. Willy Masfrancx

    Het kan nuttig zijn om een “Hoofd.Children.Clear();” toe te voegen voor “StackLayout Stapel = new StackLayout();” om alle rijen te verwijderen en zo de volgende keer meer of minder rijen te kunnen aanmaken

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.