Zoeken

Plugins tutorial 2: opslag data

Deze tutorial gaat verder waar de basis tutorial gebleven was. Volg deze dus eerst, of download de code om direct te beginnen met deze tutorial, op basis van de code uit de vorige tutorial.

De code voor deze tutorial is hier te downloaden.

Inhoud:

Stap 1: aanmaken data model

Als eerste stap maken we de C# classes waarin de data voor onze plugin zal worden opgeslagen. Deze classes zullen ook worden gebruikt om de data naar XML om te zetten zodat die op schijf opgeslagen kan worden.

Voeg een map toe in de projectstructuur, “Models”. Dit is optioneel, maar voor het overzicht is het handig. Voeg in deze map een class toe, bv. met de naam “GroenBegrenzerDataModel”. Maak de class public. Voeg een tweede class toe, bv. “GroenBegrenzerSignaalGroepDataModel”. Maak ook deze class public, en voeg drie properties toe, zodat de class er uiteindelijk als volgt uit ziet:

public class GroenBegrenzerSignaalGroepDataModel
{
    public string SignaalGroepNaam { get; set; }
    public bool BegrensGroen { get; set; }
    public int MaximaalGroen { get; set; }
}

De SignaalGroepNaam zullen we gebruiken om de data uit deze class te relateren aan signaalgroepen uit het hoofd model van TLCGen. De overige properties gebruiken we om onze eigen instellingen op te slaan: het al dan niet toepassen van de groen begrenzer op een fase, en de ingestelde begrenzing.

In de GroenBegrenzerDataModel voegen we nu een lijst toe met GroenBegrenzerSignaalGroepDataModel elementen. We decoreren de class ook met het XmlRoot attribuut (en voegen het juiste using statement toe), zodat later duidelijk is onder welke naam de data van de plugin opgeslagen moet worden binnen de XML data van een TLCGen regeling:

using System.Collections.Generic;
using System.Xml.Serialization;

namespace TLCGen.Plugins.GroenBegrenzer.Models
{
    [XmlRoot("GroenBegrenzer")]
    public class GroenBegrenzerDataModel
    {
        [XmlArray(ElementName = "SignaalGroep")]
        public List<GroenBegrenzerSignaalGroepDataModel> SignaalGroepen { get; set; }

        public GroenBegrenzerDataModel()
        {
            SignaalGroepen = new List<GroenBegrenzerSignaalGroepDataModel>();
        }
    }
}

Het data model is nu klaar om binnen de plugin te dienen als model voor de opslag van instellingen. Tevens is het direct geschikt om te worden gebruikt bij het omzetten naar XML.

Stap 2: opslaan en inladen van data

Wanneer de gebruiker van TLCGen een nieuwe of bestaande regeling opent, moeten we hierop reageren:

  • Bij een nieuwe regeling moeten we een nieuw model aanmaken en ontsluiten
  • Bij een bestaande regeling:
    • Kijken we of er data voor ons in de regeling zit, en laden die in
    • Of, als die er niet is, moeten we een nieuw model aanmaken en ontsluiten

Om dit te bereiken moeten we twee dingen doen:

  • Reageren op het laden door TLCGen van een nieuwe regeling, waarbij nieuwe betekent nieuw binnen de applicatie: ofwel de gebruiker heeft een nieuwe regeling aangemaakt, ofwel een bestaande geopend.
  • Zorgen dat de plugin data worden ingeladen vanuit XML, en wordt weggeschreven naar XML.

We beginnen met het laatste

Stap 2a: inladen uit en opslaan naar XML

Om data in te laden en op te slaan implementeren we het interface “ITLCGenXMLNodeWriter”. Dit interface heeft twee functies, een voor inladen en een voor opslaan van data. De code in deze functies is ervoor verantwoordelijk de juiste XML data op te slaan in, en op te halen uit een op te slaan/in te laden TLCGen bestand. Het is raadzaam hiervoor als basis code te gebruiken uit een bestaande TLCGen plugin, zoals de RangeerElementen plugin, zodat hier geen fouten worden gemaakt.

Als eerste breiden we de code bij het TLCGenPlugin attribuut boven de plugin class uit, en voegen de interface toe. De declaratie van de class ziet er dan als volgt uit:

[TLCGenPlugin(TLCGenPluginElems.TabControl | TLCGenPluginElems.XMLNodeWriter)]
[TLCGenTabItem(-1, TabItemTypeEnum.MainWindow)]
public class GroenBegrenzerPlugin : ITLCGenTabItem, ITLCGenXMLNodeWriter

Vervolgens voegen we een field toe in de GroenBegrenzerPlugin class, van het type GroenBegrenzerDataModel. Hier slaan we de actuele data op.

private GroenBegrenzerDataModel _dataModel;

De implementatie van de interface ziet er vervolgens als volgt uit:

#region ITLCGenXMLNodeWriter

public void GetXmlFromDocument(XmlDocument document)
{
    _dataModel = null;

    foreach (XmlNode node in document.FirstChild.ChildNodes)
    {
        if (node.LocalName == "GroenBegrenzer")
        {
            _dataModel = XmlNodeConverter.ConvertNode<GroenBegrenzerDataModel>(node);
            break;
        }
    }
    if (_dataModel == null)
    {
        _dataModel = new GroenBegrenzerDataModel();
    }
}

public void SetXmlInDocument(XmlDocument document)
{
    XmlDocument doc = TLCGenSerialization.SerializeToXmlDocument(_dataModel);
    XmlNode node = document.ImportNode(doc.DocumentElement, true);
    document.DocumentElement.AppendChild(node);
}

#endregion // ITLCGenXMLNodeWriter

Belangrijk is hier de string bij de code “if(node.LocalName == …”. Hier moet dezelfde string staan als die is opgegeven bij het “XmlRoot” attribuut van het data model, anders gaat het mis.

De class is nu in staat data op te slaan en later weer in te laden. Indien in een .tlc file geen XML voor de plugin aanwezig is, maakt de code een nieuwe, lege versie aan van het data model.

Stap 2b: zorgen dat het data model kloppend wordt voor een bestaande regeling

We gebruiken nu de Controller property van de GroenBegrenzerPlugin class: wanneer deze een nieuwe waarde krijgt betekent dit dat TLCGen een nieuwe controller heeft geladen. Dit gebeurt ná het inladen van de XML. Indien de waarde van de property niet null is, kunnen we hierop reageren: we moeten kijken of de regeling signaalgroepen heeft, en zo ja, of alle signaalgroepen uit de regeling, ook in het data model van de plugin terug komen. Hiermee stellen we zeker dat een en ander ook goed verloopt bij bestaande regelingen.

In code:

“`csharp
public ControllerModel Controller
{
get => _controller;
set
{
_controller = value;
if(_controller != null)
{
UpdateModel();
}
else
{
_dataModel = null;
}
}
}
“`

We plaatsen het up to date brengen van het model in een aparte functie, UpdateModel(), t.b.v. mogelijk hergebruik van die code.

private void UpdateModel()
{
    if (_controller != null && _dataModel != null)
    {
        foreach(var nfc in _controller.Fasen.Where(x => _dataModel.SignaalGroepen.All(x2 => x2.SignaalGroepNaam != x.Naam)))
        {
            _dataModel.SignaalGroepen.Add(new GroenBegrenzerSignaalGroepDataModel { SignaalGroepNaam = nfc.Naam });
        }
        var oldFcs = _dataModel.SignaalGroepen.Where(x => _controller.Fasen.All(x2 => x2.Naam != x.SignaalGroepNaam)).ToList();
        foreach (var ofc in oldFcs)
        {
            _dataModel.SignaalGroepen.Remove(ofc);
        }
    }
}

Hiermee stellen we zeker, dat bij het openen van een regeling, de signaalgroepen tussen het model van TLCGen, en dat van onze plugin, volledig gelijk lopen. Wat echter nog niet geregeld is, is reageren op toevoegen en verwijderen van signaalgroepen door de gebruiker binnen TLCGen. Dit is het onderwerp van de volgende tutorial: reageren op events binnen TLCGen.

De werking van de opslag van data kunnen we evenwel wel controleren:

  • Maak een nieuwe regeling, en voeg bv 4 fasen toe. Sla de regeling op. Open vervolgens de .tlc file met een tekst editor. Ergens onderaan de XML zal een node staan:
  <GroenBegrenzer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:nil="true" />

Sluit de regeling af, en open die opnieuw. Wijzig iets kleins, zodat je hem weer op kunt slaan, en sla hem weer op. De XML is nu gewijzigd:

<GroenBegrenzer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SignaalGroep>
    <GroenBegrenzerSignaalGroepDataModel>
      <SignaalGroepNaam>01</SignaalGroepNaam>
      <BegrensGroen>false</BegrensGroen>
      <MaximaalGroen>0</MaximaalGroen>
    </GroenBegrenzerSignaalGroepDataModel>
    <GroenBegrenzerSignaalGroepDataModel>
      <SignaalGroepNaam>02</SignaalGroepNaam>
      <BegrensGroen>false</BegrensGroen>
      <MaximaalGroen>0</MaximaalGroen>
    </GroenBegrenzerSignaalGroepDataModel>
    <GroenBegrenzerSignaalGroepDataModel>
      <SignaalGroepNaam>03</SignaalGroepNaam>
      <BegrensGroen>false</BegrensGroen>
      <MaximaalGroen>0</MaximaalGroen>
    </GroenBegrenzerSignaalGroepDataModel>
    <GroenBegrenzerSignaalGroepDataModel>
      <SignaalGroepNaam>04</SignaalGroepNaam>
      <BegrensGroen>false</BegrensGroen>
      <MaximaalGroen>0</MaximaalGroen>
    </GroenBegrenzerSignaalGroepDataModel>
  </SignaalGroep>
</GroenBegrenzer>

Ofwel, de code werkt, want de betreffende signaalgroepen zijn in ons plugin data model toegevoegd.

Inhoudsopgave