Mobiele apps programmeren met Xamarin – De anatomie van een Xamarin-project

print

Inhoud


Wat vooraf ging

We hebben reeds kennis gemaakt met wat een Xamarin eigenlijk is en hoe u een Xamarin-project opstart.

Nu gaan we zien hoe een Xamarin-project, meer specifiek een Xamarin.Forms-project, is opgebouwd.

U Opent het Xamarin-project dat u reeds aangemaakt hebt opnieuw of u maakt een nieuw Xamarin-project.

Als u het project uitvoert krijgt u onderstaand, heel eenvoudig programmaatje te zien.


De 4 projecten

Een nieuw Xamarin-project (hier met de naam App1) bevat standaard 4 projecten.

  • App1 (Portable) bevat alle programmeercode die gedeeld wordt met de andere projecten. Hier gaan we het vaakst in werken.
  • App1.Android bevat de specifieke Android-code en refereert naar App1 (Portable).
  • App1.iOS bevat de specifieke iOS-code en refereert naar App1 (Portable).
  • App1.UWP bevat de specifieke Windows-code en refereert naar App1 (Portable). Omdat dit het laatst/huidig opgestart project is staat dit in het vet.

Let op, vanaf Visual Studio 2017 zal u de verwijzing (Portable) niet meer zien.

We bespreken nu de 4 projecten meer in detail.


App1 (Portable)

Hier komt de programmeercode die eenmalig geprogrammeerd wordt en nadien gerefereerd (opgenomen) door de andere 3 projecten (Android, iOS en UWP).

Er zijn 5 onderdelen:

  • Properties
  • References
  • App.xaml met de achterliggende C#-code (code-behind) in App.xaml.cs
  • MainPage.xaml met de achterliggende C#-code (code-behind) in MainPage.xaml.cs
  • packages.config

We gaan nu deze onderdelen iets nader bekijken.

Properties

De Properties bevatten de eigenschappen van het project.

  • Dubbelklik op Properties om de eigenschappen te zien.

U kunt al eens rondkijken. Later, ten gepaste tijden, zullen we dit scherm opnieuw nodig hebben voor het wijzigen van bepaalde eigenschappen.

Merk al op dat de namespace de naam App1 heeft. Een namespace wordt gebruikt om projecten, bij mekaar horende klassen, te organiseren, een naam te geven. U zou (voorlopig) eenvoudig kunnen stellen dat de namespace de naam is van dit project. U zal het begrip namespace nog tegenkomen in deze post.

  • De instellingen, die via de eigenschappen worden ingesteld, worden bewaard in het bestand AssemblyInfo.cs. Bent u nieuwsgierig, wel, standaard ziet AssemblyInfo.cs er als volgt uit:
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following 
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("App1")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("App1")]
[assembly: AssemblyCopyright("Copyright ©  2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers 
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

U kunt de eigenschappen ook rechtstreeks in dit bestand wijzigen.

References

References bevat de assemblies (bibliotheken) die gebruikt worden in dit project. U hebt standaard de volgende refenties:

  • .NET (de standaardbibliotheek van .NET)
  • Xamarin.Forms.Core, Xamarin.Forms.Platform, Xamarin.Forms.Xaml de standaardbibliotheken nodig om met Xamarin te kunnen werken.

References worden toegevoegd wanneer u een NuGet Package toevoegt. Hoe u dit doet ziet u verder in deze post.

App.xaml en App.xaml.cs

Als u een pagina toevoegt aan uw app (u ziet verderop hoe u dit doet) komt die in 2 delen:

  • XAML: bevat de opmaakcode van de pagina.
  • CS: bevat de C#-programmeercode van de pagina. De zogenaamde code-behind.

Beiden werken samen als één geheel.

Deze werkwijze laat toe om een pagina visueel op te bouwen gebruikmakend van XAML (te vergelijken met HTML voor webpagina’s). Het programmeren gebeurt dan achterliggend in C#.

App.xaml

App.xaml bevat de Resources voor uw project, denk aan stijlen (te vergelijken met CSS voor webpagina’s), templates,… die doorheen het volledige project (her)gebruikt kunnen worden.

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="App1.App">
	<Application.Resources>

		<!-- Application resource dictionary -->

	</Application.Resources>
</Application>

Zoals u ziet zijn er momenteel nog geen resources toegevoegd. Later (in de module “complexe functionaliteiten” leert u hoe u hier resources, zoals stijlen, kunt toevoegen).

App.xaml.cs

App.xaml.cs bevat de programmeercode die wordt uitgevoerd als de app wordt opgestart.

using Xamarin.Forms;

namespace App1
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();

            MainPage = new App1.MainPage();
        }

        protected override void OnStart()
        {
            // Handle when your app starts
        }

        protected override void OnSleep()
        {
            // Handle when your app sleeps
        }

        protected override void OnResume()
        {
            // Handle when your app resumes
        }
    }
}

Komt deze code u reeds bekent voor? Kan best want ze stond al heel de tijd open op uw scherm!

Even een korte bespreking van deze code.

  • Eerst stelt de code dat er gebruik moet gemaakt worden van de bibliotheek (namespace om correct te zijn) Xamarin.Forms. Dit gebeurt via:

using Xamarin.Forms;

  • Vervolgens komt de huidige namespace waar we onze code aan toevoegen, zeg maar, de naam van het project App1.

namespace App1
{
}

  • Vervolgens komt de basisklasse App van het type Application. Deze Class is public (beschikbaar voor heel het project) en partial (deze klasse is gedeeltelijk en heeft dus ook nog een ander deel, namelijk de bijhorende XAML-code).

public partial class App : Application
{
}

  • Vervolgens komt de zogenaamde constructor van de klasse App. De constructor wordt uitgevoerd op het moment de klasse wordt geconstueerd, in dit geval wanneer de app wordt gestart, en bevat de code die dan moet worden uitgevoert.

public App()
{
}

De constructor bevat 2 instructies:

  • De eerste initialiseert het bijhorende XAML-component. Deze instructie vindt u steeds terug in iedere code-behind die samengaat met XAML-code. U moet deze instructie laten staan.

InitializeComponent();

  • De tweede instructie stelt de hoofdpagina in:

MainPage = new App1.MainPage();

De volledige constructor wordt dus:

public App()
{
InitializeComponent();
MainPage = new App1.MainPage();
}

Tenslotte staan er 3 functies klaar die reeds bestaan maar die we kunnen overschrijven met eigen code indien we dit wensen:

  • OnStart() wordt uitgevoerd wanneer de applicatie wordt geladen.
  • OnSleep() wordt uitgevoerd wanneer de applicatie in slaapstand gaat.
  • OnResume() wordt uitgevoerd wanneer de applicatie terug uit slaapstand komt.

protected override void OnStart
{
// Handle when your app starts
}

protected override void OnSleep()
{
// Handle when your app sleeps
}

protected override void OnResume()
{
// Handle when your app resumes
}

Wie al een stapje verder wilt gaan kan onderstaande video bekijken.

Mainpage.xaml en Mainpage.xaml.cs

We hebben net gezien dat de applicatie bij het opstarten een specifieke pagina laadt:

MainPage = new App1.MainPage();

Deze pagina moet dus ook reeds bestaan.

Mainpage.xaml

Dit is de XAML van de MainPage:

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

	<Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" />

</ContentPage>

Merk op dat dit een ContentPage is die het Label bevat dat de tekst “Welcome to Xamarin Forms!” in het midden van de pagina weergeeft:

	<Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" />

Mainpage.xaml.cs

Deze Mainpage.xaml.cs bevat geen toegevoegde programmeercode, enkel de basisstructuur. Deze structuur staat klaar om code aan toe te voegen.

using Xamarin.Forms;

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

Laten we nog even kort deze structuur bespreken.

U maakt opnieuw gebruik van de bibliotheek/namespace Xamarin.Forms.

using Xamarin.Forms;

Vervolgens komt de namespace van ons eigenlijke project App1.

namespace App1
{
}

Deze bevat de publieke gedeelde Class MainPage maar deze keer van het type (meer correct: overgeërfd van de klasse) ContentPage.

public partial class MainPage : ContentPage
{
}

De constructor van de Mainpage doet niets meer dan de bijhorende XAML-component initialiseren.

public MainPage()
{
InitializeComponent();
}

Omdat u deze structuur nog vaker zal tegenkomen herneem ik hem nog eens in zijn geheel.

using Xamarin.Forms;

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

ContentPage is maar één van de mogelijke Pages, wie nu al kennis wilt maken met andere Pages (en Views) kan onderstaande video bekijken.

packages.config

package.config is een xml-bestand dat informatie bevat over de geïsntalleerde NuGet-packages. De inhoud van dit bestand wordt automatisch bijgewerkt door de NuGet package manager en mag niet manueel worden aangepast.


App1.Android, App1.iOS en App1.UWP

App1.Android, App1.iOS en App1.UWP zijn de 3specifieke projecten voor respectievelijk, u raadt het al,  Android, iOS en UWP (Windows 10).

Als we een paar specifieke details buiten beschouwing laten en ons focussen op de grote lijnen, dan hebben deze drie projecten veel gemeenschappelijk. Ze hebben alle drie:

  • een gelijkaardige mappenstructuur.
  • een manier om het basisproject (App1 (portable)) aan te roepen, zij het ietwat anders voor elk project.

De mappenstructuur

Zowel het Android-, iOS– als UWP-project kennen dezelfde onderliggende mappen.

MapOmschrijving
PropertiesDe Properties bevatten de eigenschappen van het project. Dubbelklik op Properties om de eigenschappen te zien. De instellingen, die via de eigenschappen worden ingesteld, worden bewaard in het bestand AssemblyInfo.cs. De mogelijke instellingen zullen verschillen per project (Android, iOS en UWP)
ReferencesDe map References bevat de assemblies (bibliotheken) die gebruikt worden in het project. De references zullen specifiek zijn per project (Android, iOS en UWP).
AssetsIn deze map komen toevoegingen die het project nodig heeft. Denk aan tekstbestanden, databestanden, fonts,.... iOS heeft deze map niet standaard.
ResourcesIn deze map komen bronnen terecht die het project gebruikt. Ik denk dan in de eerste plaats aan afbeeldingen,.... UWP heeft deze map niet standaard.

Merk op dat bij de References van ieder project een verwijzing staat naar het “algemene basisproject”. Op deze manier wordt het “algemene basisproject” opgenomen in, gedeeld met, de andere projecten.

Het basisproject aanroepen vanuit het Android-project via MainActivity.cs

Het basisproject App1 (Portabel) wordt in het Android-project aangeroepen in het bestand MainActivity.cs.

U ziet het bestand hieronder. Ik ga de code niet in detail bespreken, behalve de eigenlijke aanroep via:

LoadApplication(new App());

Deze aanroep gebeurt in de OnCreate() methode die uitgevoerd wordt voor de Xamarin.Forms applicatie wordt geladen.

protected override void OnCreate(Bundle bundle){
}

using System;

using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace App1.Droid
{
    [Activity(Label = "App1", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App());
        }
    }
}

Het basisproject aanroepen vanuit het iOS-project via AppDelegate.cs

Het basisproject App1 (Portabel) wordt in het iOS-project aangeroepen in het bestand AppDelegate.cs.
U ziet het bestand hieronder. Ik ga de code niet in detail bespreken, behalve de eigenlijke aanroep via:

LoadApplication(new App());

Deze aanroep gebeurt in de FinishedLaunching() methode die uitgevoerd wordt voor de Xamarin.Forms applicatie wordt geladen.

public override bool FinishedLaunching(UIApplication app, NSDictionary options){
}

using System;
using System.Collections.Generic;
using System.Linq;

using Foundation;
using UIKit;

namespace App1.iOS
{
    // The UIApplicationDelegate for the application. This class is responsible for launching the 
    // User Interface of the application, as well as listening (and optionally responding) to 
    // application events from iOS.
    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {
        //
        // This method is invoked when the application has loaded and is ready to run. In this 
        // method you should instantiate the window, load the UI into it and then make the window
        // visible.
        //
        // You have 17 seconds to return from this method, or iOS will terminate your application.
        //
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init();
            LoadApplication(new App());

            return base.FinishedLaunching(app, options);
        }
    }
}

Het basisproject aanroepen vanuit het UWP-project via MainPage.xaml.cs

Het basisproject App1 (Portabel) wordt in het iOS-project aangeroepen in het bestand MainPage.xaml.cs.
U ziet het bestand hieronder. Ik ga de code niet in detail bespreken, behalve de eigenlijke aanroep via:

LoadApplication(new App1.App());

Deze aanroep gebeurt in de MainPage() methode die uitgevoerd wordt voor de Xamarin.Forms applicatie wordt geladen.

public MainPage(){
}

namespace App1.UWP
{
    public sealed partial class MainPage
    {
        public MainPage()
        {
            this.InitializeComponent();
            LoadApplication(new App1.App());
        }
    }
}

Een NuGet package toevoegen

NuGet is een package manager voor Microsoft development platforms zoals .NET. U kunt deze pakketten installeren om zo de mogelijkheden van uw project uit te breiden.

  • Om een NuGet package te installeren klikt u rechts op het project waar u het NuGet package wilt voor installeren of u klikt rechts op de Solution om het NuGet package meteen te installeren voor alle projecten.
  • Vervolgens klikt u op Manage NuGet packages for Solution….

  • Selecteer het NuGet package dat u wil installeren (hier Newtonsoft.Json).
  • Vink alle projecten aan waarvoor u het wilt installeren (hier allemaal).
  • Klik op Install.

Onderstaande video toont een volledige installatie (zij het in Visual Studio 2015 en voor een ASP.NET project maar in weze verschilt dit niet).


Een pagina toevoegen

Een app met maar één pagina is niet zo spectaculair. U moet dus meerdere pagina’s kunnen toevoegen aan uw project. Hoe u dit programmatorisch afhandelt zien we laten, nu toon ik enkel hoe u een pagina kunt toevoegen.

  • Klik rechts op het basisproject App1 (Portable).
  • Klik op Add – New Item ….

  • Selecteer links Xamarin.Forms.
  • Kies welk item u wenst toe te voegen, bijvoorbeeld een Content Page.
  • Kies een geschikte naam (ik laat Page1.xaml gewoon staan in dit test-project, al is het uiteraard geen goede naam).
  • Klik op Add.

  • Klik op Add.
  • Er is een Page1.xaml en een Page1.xaml.cs toegevoegd.


Afbeeldingen toevoegen

Ik bespreek hier niet hoe afbeeldingen worden weergegeven (geprogrammeerd) binnen uw app (dit wordt later besproken), maar hoe ze worden toegevoegd aan het project.

Afbeeldingen kunnen ter beschikking worden gesteld aan uw app op 3 manieren:

  • Via zijn URL (Uniform Resource Locator of zijn locatie op het Internet).
  • In een lokale map binnen de mappenstructuur van het project.
  • Embedded” (geïntegreerd) in het project zelf.

Indien u een afbeelding “aanspreekt” via zijn URL wordt de afbeelding zelf niet toegevoegd aan het project en ze we klaar met dit te bespreken. Ik ga het dus hebben hoe u een afbeelding toevoegt aan een lokale map of hoe u een afbeelding toevoegt als een “embedded resource”.

Lokale map

Bekijk afbeeldingen hier vooral als icoontjes of logo’s en niet zozeer als “echte” foto’s. Foto’s kunnen het best ergens op het internet staan en via de URL aangesproken worden (of een beperkt, vast, aantal kunnen “embedded” zijn.

UWP

U kunt afbeeldingen gewoon direct onder het UWP-project plaatsen maar beter is ze toch in een mapje te plaatsen.

  • Klik rechts op het UWP-project en kies Add – New Folder.
  • Geef vervolgens een naam aan de folder (bv. Images).

  • Klik rechts op de net aangemaakte map en kies Add – Existing item….

  • Selecteer de gewenste afbeelding(en).
  • De afbeelding(en) is toegevoegd aan het project. Kijk ook even naar de properties/eigenschappen. Vooral naar de Build Action die “gewoon” op Content staat.

Android

Het Android-project heeft reeds de nodige mappen aangemaakt. U vindt deze onder de map Resources. U voegt de afbeelding, zoals hierboven beschreven, toe aan de gewenste map.

Standaard bevat iedere map al een afbeelding icon.png.

  • Selecteer een afbeelding icon.png en kijk ook even naar de properties/eigenschappen. De Build Action heeft nu de waarde AndroidResource.

Waarom die verschillende mappen?

Android heeft deze verschillende mappen aangemaakt om tegemoet te komen aan de verschillende eisen en mogelijkheden van de devices (smartphone, tablet,…). Elke map is bedoeld voor een specifiek “formaat” (High- en low-DPI versies). U zult dus uw afbeelding ook meerdere keren, in meerdere formaten, moeten toevoegen. De juiste afbeelding wordt “at runtime” (tijdens het werken met de app) gekozen. Het volstaat dus de verschillende formaten in de juiste mappen aan te bieden en Xamarin zorgt ervoor dat de juiste afbeelding voor het juiste toestel (dpi) gekozen wordt.

Onderstaande tabel toont de betekenis van de verschillende formaten, meer gedetailleerde informatie vindt u bij Android Material Design. Of wie alles nog meer in detail wilt kan hier terecht.

Screen resolutiondpiPixel ratioPixels
xxxhdpi6404.0400 x 400
xxhdpi4803.0300 x 300
xhdpi3202.0200 x 200
hdpi2401.5150 x 150
mdpi1601.0100 x 100

iOS

Alle afbeelding binnen het iOS-project komen eveneens in de map Resources terecht maar nu heeft de propertie Build Action de waarde BundleResource.

Kent iOS dan geen verschillende formaten zoals Android?

Tuurlijk wel, oorspronkelijk had de originele iPhone een resolutie van 320 x 480. Met de huidige “grote” iPad spreken we over heel andere waarden. Dus iOS kent hetzelfde probleem (verschillende formaten en dpi’s) als Android en past eenzelfde oplossing toe (namelijk het automatisch selecteren van de geschikte afbeelding in het geschikte formaat) alleen doet iOS het niet met verschillende mappen maar met aangepaste naamgeving. De basisnaam wordt uitgebreid met een vaste “code”. Onderstaande tabel duidt de mogelijke “codes”, maar wilt u nog meer weten kijk dan eens hier.

"Code"Definite
@2xDe 2 x, Retina resolutie formaat van de afbeelding. Bv. als Image.png een resolutie van 320 x 480 pixels heeft, dan zal Image@2x.png de 640 x 960 pixels-versie van dezelfde afbeelding zijn. @2x wordt gebruikt op de Retina displays.
@3xDe 3 x, HD resolutie formaat van de afbeelding. Bv. als Image.png een resolutie van 414 x 736 pixels heeft, dan zal Image@3x.png de 1242 x 2208 pixels-versie van dezelfde afbeelding zijn. @3x is toegevoegd om iPhone 6s (en volgende) met een HD resolution display te ondersteunen.
~iphoneiOS gebruikt deze versie van de afbeelding op een iPhone of IPod. Combinaties als Image@2x~iphone.png zijn eveneens mogelijk.
~ipadiOS gebruikt deze versie van de afbeelding op een iPad.

Let op, u moet zelf de verschillende formaten toevoegen aan de map onder hun juiste naam. Xamarin (iOS) zal op basis van de naam de geschikte keuze maken maar zal zelf niet de resolutie de afbeelding aanpassen.

“Embedded” afbeeldingen

“Embedded” afbeeldingen worden net als lokale afbeeldingen toegevoegd aan het project maar worden niet in een map gestoken maar rechtstreekst opgenomen in de “assemblie” van het project zelf (zeg mar in het .exe-bestand zelf).

Zoals eerder gesteld kunt u afbeeldingen het best op internet hosten maar… dan moet u wel steeds connectie hebben met internet. Nu, vele apps werken met een databank in de cloud waarvoor ook internet nodig is, dus het hebben van internet is een “must” voor vele apps. Maar stel dat u toch een aantal afbeeldingen steeds wilt kunnen weergeven, ook zonder internet?

Een oplossing is ze in lokale mappen te plaatsen. Dit kan, al geldt hier de opmerking dat u in deze lokale mappen vooral icoontjes en logo’s plaatst (in hun verschillende formaten).

Voor een beperkt aantal, essentiële afbeeldingen kan de “embedded” optie misschien de beste zijn. “Embedded” afbeeldingen maken als het ware deel uit van het programma zelf.

“Embedded” afbeeldingen worden toegevoegd aan het basisproject App1 (Portable) via rechts klikken en vervolgens opnieuw Add – Existing item…. De propertie Build Action moet uzelf de waarde Embedded Resource geven.

Het is nog niet de bedoeling om nu reeds te bespreken hoe u deze afbeeldingen kunt “programmeren”, dit komt later wel, maar wie nu al meer wilt weten kan hier al eens een kijkje nemen.


Verwijderen uit het project

Tot nu toe hebben we enkel zaken toegevoegd aan het project maar stel dat u ook een pagina, een afbeelding of zelfs een volledig project (bv. het Android, iOS of UWP project) wilt verwijderen dan doet u dit door het gewenste object aan te klikken met de rechter muisknop en te kiezen voor Delete.

Bij het verwijderen geldt ook hier, denk 2 keer na voor u verwijdert!


Hoor het eens van iemand ander

Onderstaande video komt even terug op wat Xamarin is en waarvoor het dient en bouwt nadien een eerste Xamarin-applicatie.

Op het einde wordt ook nog platform specifieke code toegevoegd (tekst omzetten naar gesproken tekst). Omdat we nog maar pas kennis maken met Xamarin is dit, op dit ogenblik, een stapje te ver maar later komt dit zeker aan bod. Bekijk het voorlopig als een soort van preview van wat we later ook effectief gaan programmeren.


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

  • IC BC017 – kan ICT veilig en duurzaam gebruiken
  • IC BC249 – kan de instellingen van een specifieke ontwikkelomgeving wijzigen
  • IC BC288 – kan ICT-problemen oplossen

Geef een reactie

Deze website gebruikt Akismet om spam te verminderen. Bekijk hoe je reactie-gegevens worden verwerkt.

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