Beispieldialog für das Datenmodell des ISA Dialog Managers 6

Der Beispieldialog zeigt einen Shop mit vier Fenstern (Ansichten). Die Daten sind in IDM-Records und -Variablen enthalten. Der Dialog demonstriert die neuen Funktionen, mit denen GUI-Objekte und Daten verbunden werden können. Anweisungen im Zusammenhang mit dem Datenmodell sind hervorgehoben und ausführlich kommentiert.

Hinweis

Eine der Ansichten und Datenstrukturen sowie diverse Anweisungen, die für die zu demonstrierenden Funktionen keine Rolle spielen, wurden ausgelassen, damit der Beispieldialog überschaubar bleibt und nicht zu lang wird.

Screenshots

Klicken Sie für eine größere Darstellung auf die Bilder.

Beispiel Datenmodell: Produktauswahl Beispiel Datenmodell: Produkt-Details Beispiel Datenmodell: Warenkorb Beispiel Datenmodell: Checkout

Dialog

Die beiden Schaubilder sollen Ihnen die Verbindungen zwischen Daten und Ansichten verdeutlichen, die durch die Anweisungen im Dialog hergestellt werden.

  Beispiel Datenmodell: Interaktionen zwischen den DialogenBeispiel Datenmodell: Interaktionen zwischen den Dialogen

dialog D

/* Beispiel Shop für das Datenmodell des ISA Dialog Managers. Das Datenmodell ist eine neue
 * Funktion, die im ISA Dialog Manager 6 eingeführt wird.
 *
 * Das Beispiel zeigt einen Shop, dessen Produktliste in einem Record gespeichert ist.
 * Dieser Record dient als Datenmodell für verschiedene Ansichten (Views). Außerdem wird ein
 * Datenmodell genutzt, um den Warenkorb mit den Einkäufen eines Benutzers zu verwalten.
 *
 * Rechtliche Hinweise:
 * Der Code dient ausschließlich zur Demonstration von Funktionen und ist nicht für den
 * produktiven Einsatz gedacht.
 * ISA Informationssysteme GmbH (ISA) überlässt Ihnen dieses Programm so wie es ist. ISA
 * leistet keinerlei Gewährleistung oder Support zu diesem Programm und übernimmt keinerlei
 * Haftung für Schäden, die durch den Einsatz dieses Programms entstehen.
 *
 * Hier gezeigte Funktionen können sich bis zur Fertigstellung der Version noch ändern.
 */


// Definitionen von Tiles, Colors und Defaults ausgelassen.


/* Der Record Product enthält alle Informationen zu einem Produkt.
 */ 
record Product
{
  /* Das Attribut .dataoptions[enum] steuert, wann der Record Änderungen seiner Attribute
   * an verknüpfte Ansichten (Views) weitergibt. Die Weitergabe bei jeder Änderung
   * (dopt_propagate_on_changed) wird hier ausgeschaltet, um eine mehrfache Verwendung
   * durch MWiProduct zu erleichtern.
   */
  .dataoptions[dopt_propagate_on_changed] false;

  integer Id := 0;
  string Name := "";
  object Picture := null;
  string Description := "";
  string Confection :=  "";
  integer Price := 0;

  rule boolean Get(integer Id)
  {
    this.Id := Id;
    if fail(this.Name := ProductList.Name[Id]) then
      return false;
    endif
    this.Price := ProductList.Price[Id];
    this.Picture := ProductList.Picture[Id];
    this.Description := ProductList.Description[Id];
    return true;
  }
}


/* Der Record ProductList enthält die Warenliste des Shops. Er liefert die Produktinforma-
 * tionen Id, Name, Picture, Icon,, Price und Description für diverse Ansichten (Views).
 */
record ProductList
{
  integer Id[10] := 0;
  .Id[1] := 1;
  // Id[2-9]
  .Id[10] := 10;

  string Name[10] := "";  
  .Name[1] := "Denim Jeans";
  // Name[2-9]
  .Name[10] := "Umbrella";

  object Picture[10] := null;
  .Picture[1] := TiJeans;
  // Picture[2-9]
  .Picture[10] := TiUmbrella;

  object Icon[10] := null;
  .Icon[1] := TiJeansIcon;
  // Icon[2-9]
  .Icon[10] := TiUmbrellaIcon;

  integer Price[10] := 0;
  .Price[1] := 125;
  // Price[2-9]
  .Price[10] := 19;

  string Description[10] := "...";
  .Description[1] := "Close-fitting and durable jeans for leisure and business.";
  // Description[2-9]
  .Description[10] := "Turns even a long rainy walk into fun!";
}


/* Im Record ShoppingBasket ist der Warenkorb mit den vom Benutzer ausgewählten Produkten
 * gespeichert. Der Warenkorb berechnet Gesamtzahl und -preis dieser Produkte.
 */
record ShoppingBasket
{
  integer TotalPrice := 0;
  integer TotalItems := 0;

  integer Id[0] := 0;
  string  Name[0] := "";
  integer  Price[0] := 0;
  integer  Quantity[0] := 0;

  rule void Update(integer Id, integer Count)
  {
    variable integer I;

    if (Id < 1) then
      return;
    endif
    I := this:find(.Id, Id);
    if (I < 1) then
      I := this.count[.Id] + 1;
      this.count[.Id] := I;
      this.count[.Price] := I;
      this.count[.Name] := I;
      this.count[.Quantity] := I;
      this.Id[I] := Id;
      this.Name[I] := ProductList.Name[I];
      this.Price[I] := ProductList.Price[I];
      this.Quantity[I] := 0;
    endif

    this.Quantity[I] := this.Quantity[I] + Count;
    if (this.Quantity[I] < 0) then
      this.Quantity[I] := 0;
    endif
    this:UpdateTotal();
  }
  
  rule void Clear()
  {
    this.count[.Id] := 0;
    this.count[.Name] := 0;
    this.count[.Price] := 0;
    this.count[.Quantity] := 0;
    this.TotalPrice := 0;
    this.TotalItems := 0;
  }
  
  on .Quantity changed
  {
    this:UpdateTotal();
  }

  /* Die Aktualisierungen von .TotalPrice und .TotalItems werden über Data-Changed-
   * Ereignisse an die verknüpften Ansichten weitergegeben.
   */
  rule void UpdateTotal()
  {
    variable integer I, N:=0, P:=0, Q;

    for I:=1 to this.count[.Id] do
      Q := this.Quantity[I];
      N := N + Q;
      P := P + this.Price[I] * Q;
    endfor
    this.TotalPrice := P;
    this.TotalItems := N;
  }
}

// ...

/* Die Variable ActiveProductId wird als Datenmodell verwendet. Sie steuert die 
 * Sensitivität von ImDetail und ImQuickAdd im Hauptfenster WiShop.
 */
variable integer ActiveProductId := 0;


/* Detailansicht eines Produkts, von der mehrere Instanzen existieren können. Jede Instanz
 * wird mit einem Record Product als Datenmodell verknüpft und mit den Daten aus dem Record
 * befüllt, wenn sie sichtbar gemacht wird. 
 */
model window MWiProduct
{
  // ...
  .title "Product Details";
  .visible false;
  
  integer Id := 0;
  
  /* Hier wird die Ansicht mit dem Datenmodell verbunden. Im Id-Attribut der Ansicht wird
   * die Produkt-Id gespeichert. Dies wird durch eine Definition .dataget[A] B definiert,
   * wobei A das Attribut der Ansicht ist, das den Wert von Attribut B des Datenmodells
   * erhalten soll.
   * Dataget definiert eine Verbindung in einer Richtung, bei der Daten vom Datenmodell an
   * die Ansicht weitergereicht werden. Sollen umgekehrt Änderungen, die in der Ansicht
   * erfolgen (z.B. durch Benutzereingaben) an das Datenmodell weitergegeben werden, kann
   * dies mit dem Attribut .dataset[] definiert werden.
   */
  .datamodel Product;
  .dataget[.Id] .Id;

  image ImPicture
  {
    // ...
    .sensitive false;
    
    /* Verbindung mit dem Picture-Attribut des Datenmodells. */
    .datamodel Product;
    .dataget[.picture] .Picture;
  }
  
  statictext { .text "Name"; /* ... */ }
  
  edittext EtName {
    // ...
    .editable false;
    
    /* Verbindung mit dem Name-Attribut des Datenmodells. Standardmäßig wird das Content-
     * Attribut der Ansicht verbunden. Deshalb braucht hier das zu verbindende Attribut der
     * Ansicht nicht explizit definiert zu werden.
     */
    .datamodel Product;
    .dataget .Name;
  }
  
  // ...

  statictext { .text "Price"; /* ... */ }

  edittext EtPrice {
    // ...
    .editable false;
    
    /* Verbindung des Content-Attributs der Ansicht mit dem Price-Attribut des Daten-
     * modells. Weil die Verknüpfung des Content-Attributs den Standard darstellt, braucht
     * .dataget nicht indiziert zu werden.
     */
    .datamodel Product;
    .dataget .Price;

    /* Hier werden die Daten für die Anzeige formatiert. Die Represent-Methode wird vom
     * Datenmodell implizit aufgerufen, wenn Daten an die Ansicht weitergereicht werden
     * sollen. Die Methode kann überschrieben werden, um die Darstellung an eigene Anfor-
     * derungen anzupassen. Eigene Implementierungen sollten am Ende die Methode des
     * Modells bzw. Defaults aufrufen, damit alle Vorgänge ausgeführt werden, die zum
     * Anzeigen der Daten notwendig sind.
     */
    :represent()
    {
      if (Attribute = .content) then
        Value := sprintf("%d Euro", Value);
      endif
      pass this:super();
    }
  }
  
  statictext { .text "&Quantity"; /* ... */ }
  
  edittext EtQuantity
  {
    // ...
    .content "1";
    .format "%3d";
    .active true;
  }
  
  
  pushbutton PbAdd
  {
    // ...
    .text "&Add to Basket";

    on select
    {
      /* Die gespeicherte Id wird für die Aktualisierung des Warenkorbs verwendet.
       */
      ShoppingBasket:Update(this.window.Id, atoi(this.window.EtQuantity.content));
      this.window.visible := false;
    }
  }
  
  pushbutton PbClose
  {
    // ...
    .text "&Close";
    
    on select
    {
      this.window.visible := false;
    }
  }
  
  rule void Open()
  {
    variable integer I;
    variable object Wi;

    if Product:Get(ActiveProductId) then
      for I:=1 to MWiProduct.count[.instance] do
        Wi :=  MWiProduct.instance[I];
        if (not Wi.visible) then
      Wi.visible := true;
          return;
    endif
       endfor
      Wi := this:create(this.dialog, true);
      Wi.title := Wi.title + " #" + MWiProduct.count[.instance];
      Wi.visible := true;
    endif
  }
}



/* Ansicht des Warenkorbs.
*/
window WiBasket
{
  // ...
  .title "Basket";
  .visible false;
  
  tablefield Tf 
  {
    // ...

    .multisel false;
    .selection[sel_row] true;
    .selection[sel_single] false;
    .selection[sel_header] false;

    .content[1,1] "Article"; 
    .content[1,2] "Price"; 
    .content[1,3] "Quantity";
    .rowheader 1;

    /* Record ShoppingBasket ist das Datenmodell dieses Tablefields. */
    .datamodel ShoppingBasket;
    
    /* Hier werden die Daten des Warenkorb-Modells auf verschiedene Spalten des Tablefields
     * verteilt. Man sollte sich an dieser Stelle noch einmal klar machen, dass Name, Id,
     * Price und Quantity des ShoppingBaskets selbst indizierte Attribute sind, also Listen
     * darstellen.
     * Beginnen wir mit dem Attribut .Id: Dieses wird an .userdata des Tablefields weiter-
     * gereicht. Da .userdata doppelt indiziert ist, muss festgelegt werden, wo sich die
     * Id-Liste innerhalb der Matrix befinden soll. Dies wird durch .dataindex[.userdata]
     * definiert. [0,1] bedeutet, dass die Id-Liste auf die erste Spalte der Userdata-
     * Matrix abgebildet wird. [4,0] würde die Liste beispielsweise auf die vierte Zeile
     * abbilden.
     * Etwas komplizierter ist es für die Attribute Name, Price und Quantity des Waren-
     * korbs, die alle im Tablefield angezeigt und auf dessen Field-Attribut abgebildet
     * werden sollen. Zur Erinnerung: Das Field-Attribut repräsentiert alle Zellen des
     * Tablefields ohne die Zeilen- und Spaltenköpfe. Da alle Attribute des Datenmodells an
     * dasselbe Attribut der Ansicht (View) weitergereicht werden, wird bei der Definition
     * der Verknüpfungen indirekt vorgegangen. Mit den Dataget-Definitionen werden die
     * Datenmodell-Attribute auf beliebige vordefinierte oder benutzerdefinierte Attribute
     * der Ansicht abgebildet, in diesem Fall auf .content, .value und .count. Dann werden
     * die gewählten Attribute der Ansicht mit den Datamap-Definitionen auf das eigentliche
     * Zielattribut .field abgebildet. Schließlich werden mit den Dataindex-Definitionen
     * den gewählten Attributen Field-Spalten zugeordnet. Insgesamt wird also die Name-
     * Liste von ShoppingBasket mit der ersten Datenspalte der Tabelle verknüpft, die
     * Price-Liste mit der zweiten und die Quantity-Liste mit der dritten Datenspalte.
     */
    .dataget[.content] .Name;
    .dataget[.userdata] .Id;
    .dataget[.value] .Price;
    .dataget[.count] .Quantity;

    .datamap[.content] .field;
    .datamap[.value] .field;
    .datamap[.count] .field;

    .dataindex[.content] [0,1];
    .dataindex[.value] [0,2];
    .dataindex[.count] [0,3];
    .dataindex[.userdata] [0,1];

    on select
    if (first(thisevent.index) > this.rowheader)
    {
        /* Der Benutzer wählt eine Position im Warenkorb aus. In EditQuantity wird die
         * Spinbox SbQuantity mit der Menge dieser Warenkorb-Position verknüpft.
         */
        this.window:EditQuantity(first(thisevent.index) - 1);
    }
  }
  
  statictext StQuantity { .text "Quantity"; /* ... */ }
  
  spinbox SbQuantity
  {
    // ...
    .minvalue 0;
    .maxvalue 100;
    .curvalue 0;
    statictext { .sensitive true; }

    /* Automatische Aktualisierung des Warenkorbs, wenn die Menge verändert wird
     * (.dataset). Die Aktualisierung erfolgt sofort beim Ereignis (dopt_apply_on_event).
     * Eine andere Option der Aktualisierung wäre dopt_apply_on_unmap, also beim
     * Unsichtbarmachen.
     */
    .datamodel ShoppingBasket;
    .dataget .Quantity;
    .dataset .Quantity;
    .dataoptions[dopt_apply_on_event] true;
  }
  
  statictext { .text "Total Price"; /* ... */ }
  
  edittext EtTotalPrice
  {
    // ...
    .editable false;
    
    /* Verknüpfung mit dem Gesamtpreis des Warenkorbs.
     */
    .datamodel ShoppingBasket;
    .dataget .TotalPrice;
    
    /* Überdefinition der Represent-Methode um den Wert zu formatieren.
     */
    :represent()
    {
      Value := sprintf("%d Euro", Value);
      pass this:super();
    }
  }

  pushbutton PbBuy
  {
    // ...
    .text "Buy...";

    /* Hier wird die automatische Konvertierung zwischen verschiedenen Datentypen genutzt,
     * um den Integer-Wert .TotalPrice in einen Boolean-Wert für das Sensitive-Attribut des
     * Pushbuttons zu wandeln.
     */
    .datamodel ShoppingBasket;
    .dataget[.sensitive] .TotalPrice;
    
    on select
    {
      this.window.visible := false;
      // Fenster zum Bezahlen des Einkaufs öffnen.
    }
  }
  
  pushbutton PbClose
  {
    // ...
    .text "Close";

    on select
    {
      this.window.visible := false;
    }
  }
  
  rule void Open()
  {
    this.SbQuantity.visible := false;
    this.StQuantity.visible := false;
    this.visible := true;
  }
  
  rule void EditQuantity(integer Pos)
  {
    /* Verknüpfen der Mengen-Spinbox in der Ansicht mit der entsprechenden Position des
     * Warenkorbs. 
     */
    this.SbQuantity.dataindex[.Quantity] := Pos;
    
    this.StQuantity.visible := true;
    this.SbQuantity.visible := true;
  }
}

// ...

/* Hauptansicht des Shops mit der Produktliste und einer Symbolleiste mit Images, über die
 * der Benutzer Einkaufsfunktionen aufrufen kann.
 */
window WiShop
{
  .title "Shop Demo (Datamodel)";
  // ...
  
  toolbar Tb
  {
    // ...
    image ImDetails
    {
      // ...
      .text "Details";
      .picture TiDetail;

      /* Nutzung der globalen Variablen ActiveProductId um die Sensititvität des Images zu
       * steuern.
       */
      .datamodel ActiveProductId;
      .dataget[.sensitive] .value;
      
      on select
      {
        MWiProduct:Open();
      }
    }
    
    image ImQuickAdd
    {
      // ...
      .text "Add";
      .picture TiAdd;
      
      .datamodel ActiveProductId;
      .dataget[.sensitive] .value;
      .dataget[.userdata] .value;
      
      on select
      {
        ShoppingBasket:Update(this.userdata, 1);
      }
    }
    
    image ImBasket
    {
      // ...
      .picture TiBasket;

      /* Anzeige der Gesamtzahl von Artikeln im Warenkorb. Wenn sich Artikel im Warenkorb
       * befinden, kann dieser durch Klicken auf das Bild angezeigt werden. TotalItems des
       * ShoppingBaskets wird automatisch in Boolean für die Sensitivität des Images
       * konvertiert.
       */
      .datamodel ShoppingBasket;
      .dataget[.text] .TotalItems;
      .dataget[.sensitive] .TotalItems;

      on select
      {
    WiBasket:Open();
      }
    }
    
    image ImPay { /* ... */ }
  }
  
  treeview Tv
  {
    // ...

    /* Zeigt die Namen und Vorschaubilder (.Icon) der einzelnen Produkte aus dem Produkt-
     * listenmodell an. Die Ids werden in .userdata[] gespeichert um bei Selektion eines
     * Produkts die globale Variable ActiveProductId auf die Id des aktiven Produkts zu
     * setzen.
     */
    .datamodel ProductList;
    .dataget[.content] .Name;
    .dataget[.userdata] .Id;
    .dataget[.picture] .Icon;

    on select
    {
      ActiveProductId := this.userdata[this.activeitem];
    }
    
    on dbselect
    {
      MWiProduct:Open();
    }
  }
  
  on close { exit(); }
}
de_DE_formal