The example dialog shows a shop with four windows (views). The data is contained in IDM records and variables. The dialog demonstrates the new functions that can be used to connect GUI objects and data. Instructions related to the data model are highlighted and commented on in detail.
Note
One of the views and data structures as well as various instructions that play no role in the functions to be demonstrated were omitted so that the example dialog remains manageable and does not become too long.
Screenshots
Click on the images for a larger view.
Dialog
The two diagrams are intended to show you the connections between data and views that are made by the instructions in the dialog.
dialog D /* Sample store for the ISA Dialog Manager data model. The data model is a new function that * will be introduced in ISA Dialog Manager 6. * * The example shows a store whose product list is stored in a record. * This record serves as a data model for various views. In addition, a data model is used * to manage the shopping cart with a user's purchases. * * Legal notice: * The code is for demonstration purposes only and is not intended for productive use. * ISA Informationssysteme GmbH (ISA) leaves this program to you as it is. * ISA does not provide any warranty or support for this program and accepts no liability for * any damage resulting from the use of this program. * * The functions shown here may be subject to change until the version is finalized. */ // 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(); } }