6.2 Kopplung zwischen Model und View

Grundsätzlich ist das Datenmodell so konzipiert, dass dem Model nicht bekannt ist, von welchen View-Objekten es genutzt wird. Das Datenschema ist für Model und View gleich: indizierte Attribute um somit Skalare, Vektoren und Matrizen zu erlauben.

Die Kopplung einer View mit einem Model geschieht über das .datamodel-Attribut:

object .datamodel[<View-Attribut>] <Model-ID>

Für die Datenübertragung vom Model zur View muss zusätzlich noch definiert werden, welches Attribut des Models auszulesen ist.

attribute .dataget[<View-Attribut>] <Model-Attribut>

Um die Rückübertragung von Daten von der View zum Model zu definieren dient das folgende Attribut:

attribute .dataset[<View-Attribut>] <Model-Attribut>

So kann exakt festgelegt werden, was im View-Objekt durch ein Model befüllt bzw. zurückreflektiert wird. Durch die Indizierung am .datamodel-Attribut ist auch die Nutzung von mehreren Datenmodellen für unterschiedliche Attribute möglich.

View-Attribute sind typischerweise, aber nicht zwangsläufig, reale Attribute des View-Objekts, also z.B. .content für einen Edittext als View-Objekt. Es empfiehlt sich, als Model-Attribute benutzerdefinierte Attribute zu verwenden, um so eine bessere Unterscheidbarkeit zwischen View- und Model-Attributen zu erreichen.

Um die Kopplung zu aktivieren bzw. wirksam werden zu lassen, muss immer das .datamodel-Attribut ohne Index gesetzt sein, sowie jeweils eine Kopplung über .dataget bzw. .dataset (mit oder ohne Index).

Hier ein Beispiel für die Nutzung von mehreren Models (Datenmodelle: RecUsers, RecManager, VarValid, LbUsers) durch mehrere Views (listbox LbUsers, edittext EtName, pushbutton PbKill).

dialog D
record RecUsers
{
  string Name[integer];
  .Name[1] := "mueller";
  .Name[2] := "schrade";
  .Name[3] := "maier";
  string Rights[integer];
  .Rights[1] := "guest";
  .Rights[2] := "user";
  .Rights[3] := "root";
}

record RecManager
{
  string CurrUser := "maier";
  boolean IsAdmin := true;
  rule boolean ChangeUser(string Name)
  {
    variable anyvalue Idx;
    Idx := RecUsers:find(.Name, Name);
    if typeof(Idx) = integer then
      this.CurrUser := Name;
      this.IsAdmin := stringpos(RecUsers.Rights[Idx], "root") > 0;
      return true;
    endif
    return false;
  }
}

color CoError "red";
variable object VarError := true;

window Wi
{
  .title "Datamodel Model-View Coupling";
  .width 200;  .height 200;
  boolean Valid := false;

  listbox LbUsers
  {
    .xauto 0;  .yauto 0;
    .ybottom 60;
    .datamodel RecUsers;
    .datamodel[.activeitem] RecManager;
    .dataget .Name;
    .dataget[.activeitem] .CurrUser;
    on select, .activeitem changed
    {
      EtName.dataindex[.content] := this.activeitem;
      VarError := null;
      RecManager:ChangeUser(EtName.content);
    }

    :represent()
    {
      if Attribute = .activeitem then
        Value := this:find(.content, Value);
      endif
      pass this:super();
    }
  }

  edittext EtName
  {
    .yauto -1;  .xauto 0;
    .ybottom 30;
    .datamodel LbUsers;
    .datamodel[.bgc] VarError;
    .dataget .content;
    .dataget[.bgc] .value;
    .dataindex[.content] 0;
    on deselect_enter
    {
      if not RecManager:ChangeUser(this.content) then
        VarError := CoError;
      endif
    }
  }

  pushbutton PbKill
  {
    .yauto -1;
    .text "Kill All Processes";
    .sensitive false;
    .datamodel RecManager;
    .dataget[.sensitive] .IsAdmin;
  }

  on close { exit(); }
}

Um gebräuchliche Kopplungen zwischen View- und Model-Attributen möglichst einfach zu definieren, kann auf die Standardvorgaben des IDM zurückgegriffen werden (im Beispiel bei EtName zu sehen, hier werden .datamodel und .dataget ohne Index verwendet). Diese Vorgaben sind in folgender Tabelle definiert:

Tabelle 19-6: Standardkopplungen von View-Attributen

Objektklasse

Standard-View-Attribut
für .dataget

Standard-View-Attribut
für .dataset

pushbutton
statictext
messagebox

.text

checkbox
radiobutton
timer

.active

.active

image
menuitem

.text

.active

filereq

.value

.value

listbox
treeview
tablefield

.content

.activeitem

notepage

.title

edittext

.content

.content

poptext

.text

.activeitem

progressbar

.curvalue

scrollbar
spinbox

.curvalue

.curvalue

menubox
toolbar
window

.title

Obiges Beispiel weist noch eine Besonderheit auf. Einmal wird ein sichtbares Objekt als Datenmodell verwendet (die View-Komponente EtName ist gekoppelt mit dem Model LbUsers).

Grundsätzlich unterstützt der IDM als Datenmodell alle Objektklassen die benutzerdefinierte Attribute erlauben, außerdem noch globale Variablen sowie Funktionen mit dem Funktionstyp datafunc. Eine Kopplung von mehreren Modellen ist ebenso möglich. Bei sichtbaren (visuellen) Objekten sollte man aber eines bedenken: die Benutzerinteraktion bewirkt kein changed-Ereignis für Attribute und somit auch keine automatische Änderungsweitergabe an das Model. Dies muss explizit bzw. durch die entsprechende Synchronisationseinstellung geschehen.

Als View-Objekt sind ebenso alle Objektklassen erlaubt, die benutzerdefinierte Attribute erlauben. Allerdings sind die Automatismen für die Synchronisation zwischen Model und View dafür ausgelegt, dass es sich bei den View-Objekten um Instanzen mit visueller Ausprägung handelt.