GuiBuilder als Runtime Environment


Der GuiBuilder kann nicht nur zur Spezifikation eingesetzt, sondern diese Oberfläche kann auch zur Laufzeit verwendet werden. Dieses ist eine ernst zu nehmende Alternative gegenüber dem Code-Generator.

Dieses setzt allerdings voraus, daß eine Dialogsteuerung (Controler) erstellt werden muß. Wie Sie den GuiBuilder in Ihre Softwareprojekte einbinden erläutert dieses Dokument.

Natürlich ist die Oberfläche des GuiBuilder auch mit dem GuiBuilder erstellt worden! (Siehe GuiBuilder.xml)

Achtung!
Dieses Dokument muß dringend überarbeitet werden, da es veraltet ist und bestimmte Vorgehensweisen sich als wenig praktikabel erwiesen haben.

Quintessenz:
Für die Spezifikationphase können GuiBuilder-Oberflächen-Definitionen bei Bedarf mit BeanShell-Scripten erweitert werden, um auf diese Art auf Benutzerereignisse zu reagieren.

Für die wirkliche Implementierung wird ein "richtiger" Controler in Java realisiert; es ist aber auch hier durchaus denkbar, das bestimmte Methoden erst mit BeanShell ausprobiert werden; da die Syntax von Java und BeanShell praktisch identisch ist, kann die mit BeanShell ausprobierte Methode bequem nach Java übernommen werden (und umgekehrt!).

Üblicherweise sind derartige Controler singleton.

Klassische Code-Sequenz:

public class MyControler {
  private GuiWindow myWindow;
  private String myXmlFileName = "MyWindow.xml";
  ...
  public void showWindow() {
    if (myWindow == null) {
     
// Fenster mit der Factory erzeugen
      myWindow = GuiFactory.getInstance().createWindow(myXmlFileName);

     
// Den Controler beim Fenster eintragen
      myWindow.setControler(this);
    }
    myWindow.show();
  }
  public void disposeWindow() {
    if (myWindow != null) {
      myWindow.dispose();
      myWindow = null;
    }
  }
  ...

  // Auf Benutzerereignisse reagieren

  public void saveActionPerformed(GuiUserEvent event) {
    JDataSet ds = event.window.getDatasetValues();
    if ( ds.hasChanges() ) { 
    ...
  }
}

Die Klasse GuiAPI sollte nicht mehr verwendet werden; es macht mehr Sinn, sich mit den Tiefen des Frameworks auseinanderzusetzen.

Zusammenfassung

Die Benutzeraktivitäten - wie Eingabe von Daten, Betätigen von Menüs oder Buttons - werden von dem GuiBuilder an einen Controler übergeben.

Sie müssen eine Dialogsteuerung erstellen, die diese Ereignisse verarbeitet.
Um die Oberfläche selbst zu steuern - wie das Öffnen und Schließen von Fenstern oder das Setzen von Daten - rufen Sie die Methoden des GuiBuilder Framework auf.

Diese Dialogsteuerung sollte rein passiv sein; also ausschließlich auf Benutzeraktivitäten reagieren.

Implementierungs-Beispiele für das Zusammenwirken der verschiedenen Komponenten finden Sie im Verzeichnis examples.

Verteilte Anwendung

Für die Anbindung der Dialogsteuerung an einen Server gibt es verschiedene Alternativen:

Client/Server
Sie greifen aus Ihrer Dialogsteuerung mit JDBC auf die Datenbank zu.
Thin Client
Die Business-Logik ist komplett auf dem Server implementiert und Sie greifen aus der Dialogsteuerung auf von einem Server angebotene Dienste zu.
Sie können hierzu die verschieden Protokolle für verteilte Anwendungen einsetzen: CORBA, RMI, HTTP, SOAP.
Ultra Thin Client
Hier liegt auch die Dialogsteuerung auf dem Server. Alle Benutzeraktionen werden an den Server durchgereicht, dort verarbeitet, und der Response auf dem Client ausgeführt.
Auf diese Art ist es möglich, einen rein generischen Client zu realisieren, der auf Grund seiner geringen Größe auch im Internet als Applet lauffähig ist.
Die Client- und Server-Adapter für die SOAP-Anbindung mit electric GLUE liegen dem GuiBuilder bereits bei.

Variante 1: Dialogsteuerung auf dem Client

Für jedes Fenster (oder für eine Gruppe von Fenstern) ist ein Controler zu implementieren, der für die Benutzerereignisse dieses Fensters zuständig ist.

Dieser Controler muß bei den zu überwachenden Fenstern registriert werden:
(oder umgekehrt)

public class MyControler {
  GuiWindow myWindow;
  ...
  myWindow = GuiFactory.getInstance().createWindow("MyScript.xml");
  myWindow.setControler(this);
  myWindow.show();

In den Oberflächen-Scripten wird definiert, welche Benutzerereignisse ausgelöst werden sollen. Hierbei lösen normale MenuItems und Buttons immer ein Ereignis aus. Im ActionCommand wird definiert, welche Methode des Controlers aufgerufen werden soll:

<Button label="Schließen" cmd="closeActionPerformed"/>

Ihr Controler muß für all diese definierten Aktionen eine Methode vorhalten, die public void ist, deren Name dem ActionCommand entspricht und den Parameter GuiUserEvent übernimmt:

public void closeActionPerformed(GuiUserEvent event) {
  ...
}

Bei allen anderen Komponenten muß das Ereignis im Script explizit formuliert sein:

<Form label="MyForm" OnClose="exitActionPerformed">

public void exitActionPerformed(GuiUserEvent event) {
  System.exit(0);
}

Natürlich dürfen verschiedene Komponenten das selbe Ereignis auslösen; z.B. ein MenuItem und ein Button in der Toolbar.

In dem UserEvent sind Referenzen auf die Komponenten enthalten, die das Ereignis ausgelöst haben; Sie können deren Methoden aufrufen, um auf das Ereignis zu reagieren.

<Item label="%Save" cmd="saveActionPerformed"/>

public void saveActionPerformed(GuiUserEvent event) {
  JDataSet ds = event.window.getDatasetValues();
  ...
}

Simple Controler

Für die Realisierung des Controlers gibt es eine weitere Alternative:
Der Controler implementiert das Interface UserActionIF.
Dieser Interface hat nur eine einzige Methode, die bei jedem Benutzerereignis vom GuiBuilder aufgerufen wird; dieses bedeutet, daß der Controler selbst herausfinden muß, welches der definierten Ereignisse tatsächlich eingetreten ist.

Variante 2: Ultra Thin Client

Bei dieser Architektur wird der generische Client des GuiBuilder eingesetzt; auf der Clientseite wird hier überhaupt nichts implementiert, der Verarbeitung der Benutzerereignisse findet auf dem Server statt.

Diese Art des Einsatzes ist dann verlockend, wenn eine Anwendung einem großen Benutzerkreis zur Verfügung gestellt werden soll (etwa im Internet). Der GuiBuilder läuft hier auch als Applet, so daß außer dem Java Plug-in auf dem Client keinerlei Installationen vorgenommen werden müssen.

Beispielanwendung

Unter guibuilder/example/ThinClient rufen sie die Prozedur StartServer.bat auf.

Anschließend kann aus einem Browser das Applet http://localhost/ThinClientApplet.html gestartet werden.

Von einem anderen Rechner geben Sie statt localhost den Namen oder die IP-Adresse des Servers an.

Wenn der Server läuft, dann hier Applet starten.

Nachtrag 5.7.2003:
Das Applet funzt mit GLUE Version 4.1 mal wieder nicht, weil die ihre Anwendung nie als Applet testen, und man dann immer Security-Exceptions um die Ohren geworfen kriegt.

Beispiel ausprobieren

Obiges Beispiel öffnet das Fenster example/ThinClient/AdressBeispiel.xml. Hier sind einige einfache Funktionalitäten implementiert:

Wie funktioniert das?

ThinClientApplet startet die Klasse de.guibuilder.adapter.ThinClientAdapter.class.

Diese Klasse entnimmt aus dem Applet die Parameter HOST und SERVICE, um unter dieser Adresse einen bind auf den gestarteten Web Service auszuführen.

Dieser Web Service implementiert das Interface GuiUserEventIF.

Als erstes ruft der ClientAdpater die Methode "started()" auf, und erwartet ein XML-Dokument mit Anweisungen, was auf der Client-Seite zu geschehen ist.
In unserem Beispiel wird

Von nun an werden alle Benutzeraktionen an den Server weiter geleitet, der (hoffentlich) entsprechend reagiert.

Implementierung des Servers

Es ist ein WebService zu erstellen, der das Interface GuiUserEventIF implementiert.

Dieser Web Service muß mit electric GLUE publiziert werden:

public class MyControler implements GuiUserEventIF {
  ...
  // start a web server on port 8004, accept messages via /glue
  HTTP.startup( "http://localhost:8004/glue" );
  // publish an instance
  Registry.publish( "urn:glueserver", this, GuiUserEventIF.class );

Der Client wird nun bei den spezifizierten Aktionen die entsprechenden Methoden ihres Controler aufrufen.

Bei größeren Anwendungen sollte dieser Controler nur als Broker fungieren: Für jedes Fenster richten Sie einen eigenen beliebig programmierten Controler ein, und dieser "Broker" reicht anhand der WindowId die Ereignisse entsprechend weiter.

Sie müssen innerhalb dieses Methodenaufrufs das Benutzerereignis verarbeiten, sowie ggf. als Reaktion hierauf dem Client eine oder mehrere Anweisungen erteilen.

So müssen Sie z.B. wenn der Benutzer den Button "Speichern" drückt, den Inhalt der Oberfläche erfragen, um diese Informationen anschließend in der Datenbank abzulegen.

...
private GuiXAPI api = new GuiXAPI();
...

public String actionPerformed(String windowId, String name, String cmd) {
  if (cmd.equals("save")) {
    api.startTrans(); // Startet eine Transaktion
    api.verifyWindow(windowId); // Benutzereingaben bitte prüfen
    api.getAllValues(windowId); // Den Inhalt des Fensters anfordern
    return api.endTrans().toString(); // Transaktion beenden als Rückgabewert
  }
}

Der Client wird nun diese Anweisungen nach und nach ausführen (geht "verify" aus irgend einem Grunde schief, wird die Transaktion abgebrochen, und der Benutzer muß erste seine Eingaben korrigieren, bevor er erneut "save" ausführt).

Die Methodenaufrufe von GuiAPI und GuiXAPI sind praktisch identisch; bis auf den Umstand, daß in GuiXAPI die Methoden keinen Returnwert haben.

Ein wesentlicher Unterschied ist jedoch, daß mit GuiXAPI "Transaktionen" von Anweisungen an den Client zusammengestellt werden. Zwischen "startTrans" und "endTrans" können beliebig viele Methodenaufrufe stehen, die vorerst nicht weiter bewirken, als daß intern ein XML-Dokument erzeugt wird. Erst bei "endTrans" wird dieses Dokument geliefert, und an den Client übertragen. Der Sinn dieses Vorgehens liegt darin, daß der Server nicht von sich aus den Client ansprechen kann; es muß vielmehr immer auf ein Ereignis aus der Oberfläche warten, und reagiert darauf mit genau einem XML-Dokument.

Wenn in einer Transaktion beliebig viele Anweisungen an den Client enthalten sein können so hat dieses auch den Vorteil, daß das Netzwerk auch nur ein Paket übertragen muß und damit Laufzeiten im Netz auch nur einmal anfallen. Der GuiBuilder funktioniert so auch im Internet!

Ergibt sich in diesem Zusammenhang, daß der Server Daten vom Client angefordert hat, werden diese auf dem Client gleichfalls in einem XML-Dokument zusammengestellt, und über die Methode replay(String xmlDocument) an der Server zurückgeschickt.

Natürlich hat auch diese Methode einen Rückgabewert, und der Client kann erneut aufgefordert werden, weitere Informationen bereitzustellen, wobei hier saubere Programmierung eine Endlosschleife vermeiden muß.

public String replay ( String xml ) throws Exception {
  Document doc = new Document( xml );
  Element root = doc.getRoot(); // Hier steht "Replay" drin.
  Elements nodelist = root.getElements(); // Menge der Antworten vom Client
  while (nodelist.hasMoreElements() ) { // Replays abarbeiten
    Element ele = nodelist.next();  // Der Nächste bitte...
    String msg = ele.getName(); // Der Typ der Client Message
    if  (msg.equals("GetAllValues")) { // Aha, endlich kommen die Daten!
      String windowId = ele.getAttributeValue("Id");
      ...

Dieses Vorgehen mag etwas umständlich erscheinen, aber das Problem liegt nunmal darin, daß Web Services keinen CallBack zulassen, der Server also keine Methoden des Clients aufrufen kann.

Pinger

Für den Fall, daß der Server dem Client eine Mitteilung machen muß, ist ThinClientAdapter mit einem "Pinger" ausgestattet:

Ein gesonderter Thread mit niedriger Priorität ruft in regelmäßigen Abständen (Default ist eine Minute) die Methode "ping()" des Servers auf. Liefert diese Methode ein Xml-Dokument zurück, wird dieses auf dem Client wie gewohnt ausgeführt (etwa einen Dialog öffnen, daß für den Benutzer eine Nachricht eingegangen ist).

Der Server hat so auch die Möglichkeit festzustellen, ob der Client überhaupt noch "lebt", oder ihn nach einer bestimmten Zeit der Inaktivität auszuloggen.

In GuiXAPI kann mit der Methode setPingInterval(int seconds)das Ping-Interval abweichend eingestellt werden.

Don't call the framework, the framework calls you!

Mit dem Attribut controler="MyControler" kann in den Gui-Scripten ein Controler für die Oberfläche definiert werden.

Dieser Controler ist entweder ein BeanShell-Script
<Form controler="MyScript.bsh" >
oder eine Java-Klasse
<Form controler="de.mydomain.MyControler" >

Zur Unterscheidung müssen BeanShell-Scripte auf ".bsh" enden.

Die Java-Klasse muß im Classpath enthalten und sollte ein Singleton sein; als erstes versucht der GuiBuilder die statische Methode getInstance() aufzurufen; wenn es diese Methode nicht gibt, wird der Default-Contructor verwendet.

Die definierten Benutzerereignisse werden an den Controler weiter gereicht.


Stand: 14.6.2003

home