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.
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
.
Für die Anbindung der Dialogsteuerung an einen Server gibt es verschiedene Alternativen:
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();
...
}
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.
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.
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.
Obiges Beispiel öffnet das Fenster
example/ThinClient/AdressBeispiel.xml
. Hier sind einige einfache
Funktionalitäten implementiert:
example/ThinClient/adrsdata.xml
).
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.
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.
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.
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