In dieser Lektion lernen Sie, wie GuiXT-Tabellen und -Strukturen als JSON
an eine .NET-Funktion übergeben werden und wie das .NET-Ergebnis als
JSON-Array zurück in eine GuiXT-Tabelle geladen wird. Als Praxisbeispiel
implementieren wir einen Routenoptimierer: Aus den
SAP-Kundenstammdaten werden Lieferadressen mit Geokoordinaten gelesen und
über die REST-API von OpenRouteService in der optimalen Reihenfolge für
eine Fahrtroute sortiert.
Das Zusammenspiel der Komponenten:
GuiXT liest Kundendaten
per Datenbank-Select in eine interne Tabellenvariable.
GuiXT übergibt die
Tabelle als JSON-Array zusammen mit dem Startpunkt an die
.NET-Funktion OptimizeDeliveries.
.NET geocodiert fehlende
Koordinaten (Nominatim/OpenStreetMap), ruft die ORS-API auf und gibt
die sortierte Route als JSON-Array zurück.
GuiXT lädt das
JSON-Ergebnis automatisch in die Zieltabelle
routetable.
1. Die Bildschirmmaske
Das GuiXT-Script definiert Eingabefelder für den Startpunkt sowie zwei
Tabellen: customer für die geladenen Kundendaten und
routetable für die optimierte Route. Der Pushbutton löst beim
Klick das InputScript calc_route.txt aus.
Die vier InputField-Anweisungen legen Felder für Straße,
Stadt, Postleitzahl und Ländercode des Startpunkts an. Straße ist optional
und wird im Script gesondert behandelt. Die Tabelle customer
hat neun Spalten, darunter latitude und
longitude, die aus einer Tabelle geoloc stammen.
Die Tabelle routetable wird durch den .NET-Rückgabewert
befüllt.
2. Das GuiXT Script (calc_route.txt)
Das InputScript wird durch den Pushbutton ausgelöst. Es legt die
Tabellenvariablen an, führt den Datenbankzugriff durch, validiert den
Startpunkt und ruft die .NET-Funktion auf.
GuiXT
// create table variable with column names
CreateTable V[customer] _
kunnr name1 name2 street city postcode country latitude longitude
// build SQL join expression
Set V[table] "knvp join kna1 on kna1~kunnr = knvp~kunnr"
Set V[table] "&V[table] join adrc on adrc~addrnumber = kna1~adrnr"
Set V[table] _
"&V[table] left outer join geoloc on geoloc~addrnumber = adrc~addrnumber"
// comma-separated field list
Set V[fields] "knvp~kunnr"
Set V[fields] "&V[fields],kna1~name1"
Set V[fields] "&V[fields],kna1~name2"
Set V[fields] "&V[fields],adrc~street"
Set V[fields] "&V[fields],adrc~city1"
Set V[fields] "&V[fields],adrc~post_code1"
Set V[fields] "&V[fields],adrc~country"
Set V[fields] "&V[fields],geoloc~latitude"
Set V[fields] "&V[fields],geoloc~longitude"
// WHERE condition (without keyword WHERE)
Set V[condition] "knvp~pernr = '00000005'"
Set V[condition] "&V[condition] and knvp~vkorg = '1010'"
Set V[condition] "&V[condition] and knvp~vtweg = '10'"
Set V[orderby] "kna1~name1"
// execute database select
Call "/guixt/dbselect" _
export.table="table" _
export.fields="fields" _
export.condition="condition" _
export.orderby="orderby" _
import.values="customer"
if not Q[ok]
Message "&V[_exception]"
endif
// validate mandatory start point fields (V[name=] means "is empty")
if V[sC=] or V[sPC=] or V[sCC=]
Message "No startpoint given"
leave
else
// Street is optional
if not V[sSt=]
Set V[Start] "&V[sSt], &V[sC], &V[sPC], &V[sCC]"
else
Set V[Start] "&V[sC], &V[sPC], &V[sCC]"
endif
endif
// create output table from a structure template
CreateStructure V[struct] stop street city postcode country
CreateTable V[routetable] include=V[struct]
// call .NET: table is serialized as JSON, result fills routetable
CallVB routetable = DataVB.RouteOptimizer.OptimizeDeliveries _
"&V[Start]<|>&V[customer]"
2.1 Tabelle anlegen mit CreateTable
CreateTable V[customer] kunnr name1 ... legt eine leere
Tabellenvariable mit den angegebenen Spalten an. Die Spaltennamen müssen
mit den Spalten der GuiXT-Bildschirmtabelle übereinstimmen, damit GuiXT
nach dem CallVB die Daten automatisch in die Anzeige
überträgt.
2.2 Datenbankabfrage mit Call "/guixt/dbselect"
Der Befehl Call "/guixt/dbselect" führt einen generischen
SQL-Select direkt auf der SAP-Datenbank aus. Die Parameter werden als
Textvariablen übergeben:
export.table:
Tabellenausdruck inklusive JOINs
export.fields:
kommagetrennte Feldliste
export.condition:
WHERE-Bedingung ohne das Schlüsselwort WHERE
export.orderby:
Sortierfeld
import.values:
Name der Zieltabellenvariablen, ohneV[...],
GuiXT löst den Namen intern auf
Nach dem Aufruf enthält V[customer] alle Ergebniszeilen.
Q[ok] zeigt Erfolg an; bei Fehler liefert
V[_exception] die Meldung. Der left outer join
mit GEOLOC stellt sicher, dass Kunden ohne Geokoordinaten trotzdem
geliefert werden, fehlende Werte werden im .NET-Code per Geocoding
nachgeschlagen.
2.3 Startpunkt validieren
Die Schreibweise V[name=] ist eine GuiXT-Abkürzung für
"Variable ist leer". Mit if V[sC=] or V[sPC=] or V[sCC=]
werden die Pflichtfelder geprüft. Straße ist optional, der
Startpunkt-String wird je nach Eingabe mit vier oder drei Teilen
zusammengebaut:
"Hauptstr. 1, Frankfurt, 60311, DE" bzw.
"Frankfurt, 60311, DE". Diese Unterscheidung wird im
.NET-Code beim Aufteilen am Komma berücksichtigt.
2.4 Ausgabestruktur mit CreateStructure und CreateTable
CreateStructure V[struct] stop street city postcode country
definiert eine benannte Felddefinition.
CreateTable V[routetable] include=V[struct] erzeugt daraus
eine leere Tabelle, ein praktischer Weg, eine Tabelle auf Basis einer
Strukturvorlage zu erstellen, ohne die Felder ein zweites Mal aufzulisten.
2.5 CallVB: Tabellen an .NET übergeben und Ergebnis
zurückladen
Wie GuiXT Tabellen als JSON serialisiert:
Wird in einem String-Parameter ein Ausdruck der Form
&V[tabellenname] verwendet und die Variable enthält eine
Tabelle, serialisiert GuiXT sie automatisch als JSON-Array. Jede
Zeile wird zu einem JSON-Objekt, die Spaltennamen werden zu den
JSON-Schlüsseln. Beispiel für zwei Zeilen in V[customer]:
In diesem Beispiel werden zwei Werte in einem einzigen String-Parameter
kombiniert: der Startpunkt-String und das JSON-Array der Kundentabelle.
Als Trennzeichen dient <|>, eine Zeichenfolge die
weder in Adressen noch in JSON vorkommt. Im .NET-Code wird am Separator
gesplittet.
Rückgabe: JSON-Array wird automatisch zur GuiXT-Tabelle:
Gibt die .NET-Funktion einen JSON-Array-String zurück und steht links vom
= eine Tabellenvariable, befüllt GuiXT die Zieltabelle
automatisch: Jedes Objekt im Array wird zur Zeile, die JSON-Schlüssel
werden den gleichnamigen Spalten zugeordnet. Fehlende Schlüssel bleiben
leer, unbekannte werden ignoriert. Kein weiterer Script-Code ist nötig.
3. Der .NET-Code
Die Klasse RouteOptimizer im Namespace DataVB
enthält drei Methoden: die öffentliche Einstiegsfunktion
OptimizeDeliveries, die Geocodierung
GeocodeAddress und die Routenoptimierung
OptimizeRoute.
3.1 Einstiegspunkt: JSON empfangen und parsen
Die Methode empfängt den kombinierten String, trennt ihn am
<|>-Separator und parst das JSON-Array der Kundentabelle
mit System.Text.Json. Für den Startpunkt wird zunächst
die Koordinate (0, 0) als Platzhalter eingetragen.
Imports System.Globalization
Imports System.Net.Http
Imports System.Text
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks
Namespace DataVB
Public Class RouteOptimizer
Private Const NominatimUrl As String =
"https://nominatim.openstreetmap.org/search"
Private Const ORSUrl As String =
"https://api.openrouteservice.org/optimization"
Private Const OrsApiKey As String = "YOUR_ORS_API_KEY_HERE"
Public Function OptimizeDeliveries(inputJson As String) As String
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Tls12
' GuiXT hat "&V[Start]<|>&V[customer]" zusammengesetzt.
' Teil 0 = Startpunkt-String, Teil 1 = JSON-Array der Tabelle.
Dim sep As String() = {"<|>"}
Dim splitParts As String() = inputJson.Split(
sep, StringSplitOptions.None)
Dim addresses As New List(Of String)()
Dim coordinates As New List(Of (lat As Double, lon As Double))()
' Startpunkt eintragen, Koordinaten kommen per Geocoding
addresses.Add(splitParts(0))
coordinates.Add((0, 0))
' JSON-Array der Kundentabelle durchlaufen
Dim doc As JsonDocument = JsonDocument.Parse(splitParts(1))
For Each el As JsonElement In doc.RootElement.EnumerateArray()
' .Replace wegen SAP-Dezimalformat (Komma statt Punkt)
Dim lat As Double = Double.Parse(
el.GetProperty("latitude").GetString() _
.Replace(",", "."),
CultureInfo.InvariantCulture)
Dim lon As Double = Double.Parse(
el.GetProperty("longitude").GetString() _
.Replace(",", "."),
CultureInfo.InvariantCulture)
Dim street As String = el.GetProperty("street").GetString()
Dim city As String = el.GetProperty("city").GetString()
Dim postcode As String = _
el.GetProperty("postcode").GetString()
Dim country As String = _
el.GetProperty("country").GetString()
addresses.Add($"{street}, {city}, {postcode}, {country}")
coordinates.Add((lat, lon))
Next
End Function
End Class
End Namespace
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace DataVB
{
public class RouteOptimizer
{
private const string NominatimUrl =
"https://nominatim.openstreetmap.org/search";
private const string ORSUrl =
"https://api.openrouteservice.org/optimization";
private const string OrsApiKey = "YOUR_ORS_API_KEY_HERE";
public string OptimizeDeliveries(string inputJson)
{
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Tls12;
// GuiXT hat "&V[Start]<|>&V[customer]" zusammengesetzt.
// Index 0 = Startpunkt-String, Index 1 = JSON-Array der Tabelle.
var sep = new string[] { "<|>" };
string[] splitParts = inputJson.Split(
sep, StringSplitOptions.None);
var addresses = new List<string>();
var coordinates = new List<(double lat, double lon)>();
// Startpunkt eintragen, Koordinaten kommen per Geocoding
addresses.Add(splitParts[0]);
coordinates.Add((0, 0));
// JSON-Array der Kundentabelle durchlaufen
JsonDocument doc = JsonDocument.Parse(splitParts[1]);
foreach (JsonElement el in doc.RootElement.EnumerateArray())
{
// .Replace wegen SAP-Dezimalformat (Komma statt Punkt)
double lat = double.Parse(
el.GetProperty("latitude").GetString()
.Replace(",", "."),
CultureInfo.InvariantCulture);
double lon = double.Parse(
el.GetProperty("longitude").GetString()
.Replace(",", "."),
CultureInfo.InvariantCulture);
string street = el.GetProperty("street").GetString();
string city = el.GetProperty("city").GetString();
string postcode = el.GetProperty("postcode").GetString();
string country = el.GetProperty("country").GetString();
addresses.Add(
$"{street}, {city}, {postcode}, {country}");
coordinates.Add((lat, lon));
}
}
}
}
Beim Parsen der Koordinaten wird .Replace(",", ".")
angewendet. Abhängig von den SAP-Systemeinstellungen kann GuiXT
Dezimalzahlen mit Komma serialisieren. CultureInfo.InvariantCulture
erzwingt anschließend die Interpretation des Punktes als Dezimalzeichen.
Kunden ohne Geokoordinaten (Latitude = 0 und Longitude = 0) werden über
die Nominatim-API von OpenStreetMap geocodiert. Die
Thread.Sleep(1100)-Pause hält das Rate-Limit ein (max.
1 Anfrage/Sekunde). Bei keinem Treffer wird ein Fallback ohne Straße
versucht. Da GuiXT .NET-Funktionen synchron ausführt, wird die async
Methode über einen synchronen Wrapper mit GetAwaiter().GetResult()
aufgerufen.
' Alle Adressen ohne Koordinaten geocodieren
For i As Integer = 0 To addresses.Count - 1
If coordinates(i).lat = 0 AndAlso coordinates(i).lon = 0 Then
coordinates(i) = GeocodeAddressSync(addresses(i))
End If
Thread.Sleep(1100)
Next
Private Function GeocodeAddressSync(address As String) _
As (lat As Double, lon As Double)
Return GeocodeAddress(address).GetAwaiter().GetResult()
End Function
Private Async Function GeocodeAddress(address As String) _
As Task(Of (lat As Double, lon As Double))
Using client As New HttpClient()
client.DefaultRequestHeaders.Add(
"User-Agent", "SAPRoutingDemo/1.0")
Dim q As String = Uri.EscapeDataString(address)
Dim url As String =
$"{NominatimUrl}?q={q}&format=json&limit=1"
Dim response As String = Await client.GetStringAsync(url)
' Fallback: Suche ohne Straße wenn kein Ergebnis
If response = "[]" Then
Dim parts As String() = address.Split(","c)
Dim p1 = parts(1).Trim()
Dim p2 = parts(2).Trim()
Dim p3 = parts(3).Trim()
Dim fallback As String = $"{p1}, {p2}, {p3}"
Dim fq As String = Uri.EscapeDataString(fallback)
url = $"{NominatimUrl}?q={fq}&format=json&limit=1"
response = Await client.GetStringAsync(url)
Thread.Sleep(1100)
End If
Dim doc As JsonDocument = JsonDocument.Parse(response)
Dim e1 = doc.RootElement.EnumerateArray().GetEnumerator()
e1.MoveNext()
Dim first As JsonElement = e1.Current
Dim lat As Double = Double.Parse(
first.GetProperty("lat").GetString(),
CultureInfo.InvariantCulture)
Dim lon As Double = Double.Parse(
first.GetProperty("lon").GetString(),
CultureInfo.InvariantCulture)
Return (lat, lon)
End Using
End Function
// Alle Adressen ohne Koordinaten geocodieren
for (int i = 0; i < addresses.Count; i++)
{
if (coordinates[i].lat == 0 && coordinates[i].lon == 0)
coordinates[i] = GeocodeAddressSync(addresses[i]);
Thread.Sleep(1100);
}
private (double lat, double lon) GeocodeAddressSync(
string address)
{
return GeocodeAddress(address).GetAwaiter().GetResult();
}
private async Task<(double lat, double lon)> GeocodeAddress(
string address)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add(
"User-Agent", "SAPRoutingDemo/1.0");
string q = Uri.EscapeDataString(address);
string url =
$"{NominatimUrl}?q={q}&format=json&limit=1";
string response = await client.GetStringAsync(url);
// Fallback: Suche ohne Straße wenn kein Ergebnis
if (response == "[]")
{
var parts = address.Split(',');
string fallback =
$"{parts[1].Trim()}, " +
$"{parts[2].Trim()}, " +
parts[3].Trim();
string fq = Uri.EscapeDataString(fallback);
url = $"{NominatimUrl}?q={fq}" +
"&format=json&limit=1";
response = await client.GetStringAsync(url);
Thread.Sleep(1100);
}
JsonDocument doc = JsonDocument.Parse(response);
double lat = double.Parse(
doc.RootElement[0].GetProperty("lat")
.GetString(),
CultureInfo.InvariantCulture);
double lon = double.Parse(
doc.RootElement[0].GetProperty("lon")
.GetString(),
CultureInfo.InvariantCulture);
return (lat, lon);
}
}
3.3 Routenoptimierung über die OpenRouteService API
OptimizeRoute baut einen JSON-Request für die ORS
Optimization API auf. Alle Kundenadressen (ab Index 1) werden als
"Jobs" übergeben, der Startpunkt (Index 0) als Fahrzeug-Startposition.
Die API antwortet mit einer optimierten Reihenfolge der Job-IDs in
routes[0].steps. Einträge vom Typ "job" sind
Kundenstopps, "start" und "end" werden
übersprungen.
Wichtig: ORS erwartet Koordinaten im Format
[lon, lat], Längengrad zuerst. Beim Konvertieren von
double-Werten in Strings muss CultureInfo.InvariantCulture
verwendet werden, damit der Dezimalpunkt (nicht Komma) ausgegeben wird.
Private Function OptimizeRouteSync( _
stops As List(Of (lat As Double, lon As Double))) _
As List(Of Integer)
Return OptimizeRoute(stops).GetAwaiter().GetResult()
End Function
Private Async Function OptimizeRoute( _
stops As List(Of (lat As Double, lon As Double))) _
As Task(Of List(Of Integer))
Using client As New HttpClient()
client.DefaultRequestHeaders.Add(
"Authorization", "Bearer " & OrsApiKey)
' Jobs-Array: jede Kundenadresse wird ein Job mit Index-ID
' ORS-Koordinatenformat: [lon, lat] (Längengrad zuerst!)
Dim jobs As New StringBuilder()
For i As Integer = 0 To stops.Count - 1
Dim sLon As String = stops(i).lon.ToString(
CultureInfo.InvariantCulture)
Dim sLat As String = stops(i).lat.ToString(
CultureInfo.InvariantCulture)
If i > 0 Then jobs.Append(",")
jobs.Append(
$"{{""id"":{i}," &
$"""location"":[{sLon},{sLat}]}}")
Next
' Fahrzeug-Startposition aus stops(0) (= Vertreter-Startpunkt)
Dim vLon As String = stops(0).lon.ToString(
CultureInfo.InvariantCulture)
Dim vLat As String = stops(0).lat.ToString(
CultureInfo.InvariantCulture)
Dim body As String =
"{""jobs"":[" & jobs.ToString() & "]," &
"""vehicles"":[{" &
"""id"":1," &
"""profile"":""driving-car""," &
$"""start"":[{vLon},{vLat}]" &
"}]}"
Dim content As New StringContent(
body, Encoding.UTF8, "application/json")
Dim httpResp As HttpResponseMessage =
Await client.PostAsync(ORSUrl, content)
Dim response As String =
Await httpResp.Content.ReadAsStringAsync()
' ORS liefert optimierte Reihenfolge in routes[0].steps
Dim doc As JsonDocument = JsonDocument.Parse(response)
Dim re = doc.RootElement.GetProperty("routes") _
.EnumerateArray().GetEnumerator()
re.MoveNext()
Dim steps As JsonElement = re.Current.GetProperty("steps")
Dim optimizedOrder As New List(Of Integer)()
For Each step As JsonElement In steps.EnumerateArray()
If step.GetProperty("type").GetString() = "job" Then
optimizedOrder.Add(
step.GetProperty("job").GetInt32())
End If
Next
Return optimizedOrder
End Using
End Function
private List<int> OptimizeRouteSync(
List<(double lat, double lon)> stops)
{
return OptimizeRoute(stops).GetAwaiter().GetResult();
}
private async Task<List<int>> OptimizeRoute(
List<(double lat, double lon)> stops)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add(
"Authorization", "Bearer " + OrsApiKey);
// Jobs-Array: jede Kundenadresse wird ein Job mit Index-ID
// ORS-Koordinatenformat: [lon, lat] (Längengrad zuerst!)
var jobs = new StringBuilder();
for (int i = 0; i < stops.Count; i++)
{
string sLon = stops[i].lon.ToString(
CultureInfo.InvariantCulture);
string sLat = stops[i].lat.ToString(
CultureInfo.InvariantCulture);
if (i > 0) jobs.Append(",");
jobs.Append(
$"{{\"id\":{i}," +
$"\"location\":[{sLon},{sLat}]}}");
}
// Fahrzeug-Startposition aus stops[0] (= Vertreter-Startpunkt)
string vLon = stops[0].lon.ToString(
CultureInfo.InvariantCulture);
string vLat = stops[0].lat.ToString(
CultureInfo.InvariantCulture);
string body =
"{\"jobs\":[" + jobs + "]," +
"\"vehicles\":[{" +
"\"id\":1," +
"\"profile\":\"driving-car\"," +
"\"start\":[" + vLon + "," + vLat + "]}]}";
var content = new StringContent(
body, Encoding.UTF8, "application/json");
var httpResp = await client.PostAsync(ORSUrl, content);
string response =
await httpResp.Content.ReadAsStringAsync();
// ORS liefert optimierte Reihenfolge in routes[0].steps
JsonDocument doc = JsonDocument.Parse(response);
JsonElement steps = doc.RootElement
.GetProperty("routes")[0]
.GetProperty("steps");
var optimizedOrder = new List<int>();
foreach (JsonElement step in steps.EnumerateArray())
{
if (step.GetProperty("type").GetString() == "job")
optimizedOrder.Add(
step.GetProperty("job").GetInt32());
}
return optimizedOrder;
}
}
3.4 Ergebnis aufbauen und als JSON an GuiXT zurückgeben
Die vom ORS gelieferte optimierte Job-Reihenfolge wird als JSON-Array
zusammengebaut. Die Feldnamen im JSON
(stop, street, city,
postcode, country) müssen exakt mit den
Spaltennamen der GuiXT-Zieltabelle routetable
übereinstimmen, GuiXT befüllt die Tabelle dann automatisch.
Eine Adresse mit Straße hat vier Teile nach dem Split am Komma,
eine ohne Straße nur drei.
Dim optimizedOrder As List(Of Integer) =
OptimizeRouteSync(coordinates)
Dim result As New StringBuilder()
result.Append("[")
For i As Integer = 0 To optimizedOrder.Count - 1
If i > 0 Then result.Append(",")
Dim parts As String() =
addresses(optimizedOrder(i)).Split(","c)
Dim street As String
Dim city As String
Dim postcode As String
Dim country As String
' 4 Teile = mit Straße, 3 Teile = ohne Straße
If parts.Length = 4 Then
street = parts(0).Trim()
city = parts(1).Trim()
postcode = parts(2).Trim()
country = parts(3).Trim()
Else
street = ""
city = parts(0).Trim()
postcode = parts(1).Trim()
country = parts(2).Trim()
End If
' Feldnamen müssen den Spaltennamen von routetable entsprechen
Dim obj As String =
$"{{""stop"":{i + 1}," &
$"""street"":""{street}""," &
$"""city"":""{city}""," &
$"""postcode"":""{postcode}""," &
$"""country"":""{country}""}}"
result.Append(obj)
Next
result.Append("]")
Return result.ToString()
var optimizedOrder = OptimizeRouteSync(coordinates);
var result = new StringBuilder();
result.Append("[");
for (int i = 0; i < optimizedOrder.Count; i++)
{
if (i > 0) result.Append(",");
var parts = addresses[optimizedOrder[i]].Split(',');
string street, city, postcode, country;
// 4 Teile = mit Straße, 3 Teile = ohne Straße
if (parts.Length == 4)
{
street = parts[0].Trim();
city = parts[1].Trim();
postcode = parts[2].Trim();
country = parts[3].Trim();
}
else
{
street = "";
city = parts[0].Trim();
postcode = parts[1].Trim();
country = parts[2].Trim();
}
// Feldnamen müssen den Spaltennamen von routetable entsprechen
string obj =
$"{{\"stop\":{i + 1}," +
$"\"street\":\"{street}\"," +
$"\"city\":\"{city}\"," +
$"\"postcode\":\"{postcode}\"," +
$"\"country\":\"{country}\"}}";
result.Append(obj);
}
result.Append("]");
return result.ToString();
Das zurückgegebene JSON hat beispielsweise folgendes Format:
Schlüsselprinzip: Die JSON-Schlüsselnamen müssen exakt mit den
Spaltennamen der GuiXT-Zieltabelle übereinstimmen, Groß-/Kleinschreibung
eingeschlossen. GuiXT erzeugt für jedes Objekt im Array eine Tabellenzeile
und ordnet die Werte den gleichnamigen Spalten zu.