Programmierhandbuch: Entwicklung von Dialog-Anwendungen

Die generelle Architektur einer Dialog-Anwendung im ERP-System ist in der nachstehenden Abbildung dargestellt. Eine Dialog-Anwendung bietet dem Benutzer eine grafische Bendienungsoberfläche (GUI), welche durch die Anwendung implementiert und verwaltet wird. Die Anwendung arbeitet intern mit einem entsprechenden Datenmodell, das auf den Business Entitys basiert und von der Anwendung visualisiert wird. Für die Integritätsprüfung von durch den Benutzer gemachten Änderungen greift die Anwendung auf „Validation“-Klassen zurück. Alle Schichten nutzen die Basisdienste des ERP-Systems.

10.1 User Interface Framework

Das User Interface Framework (UI Framework) stellt einer Anwendung die grafischen Komponenten bereit, die so genannten „VisualElements“. Dazu zählen beispielsweise Buttons, Felder, Checkboxen usw.[1] Für die Positionierung der VisualElements werden Layout-Manager genutzt. Die Steuerung erfolgt auf Grundlage von Ereignissen. Durch die Bereitstellung verschiedener „Look & Feels“ kann ein Benutzer die Oberfläche anpassen.

Obwohl das ERP-System auf der Client-Seite im WWW-Browser läuft, basiert die UI-Programmierung aussschließlich auf Java. Die Umsetzung in (D)HTML wird vollständig durch das Framework übernommen. Diese Kapselung erlaubt auch, ggf. andere Arten von Clients zu unterstützen, ohne dass dazu die Anwendungen selbst geändert werden müssten.

Das UI-Framework ist in zwei Java-Packages aufgeteilt: com.cisag.pgm.gui und com.cisag.pgm.dialog.

Typische Dialog-Anwendungen sollten nur die Klassen aus dem Package com.cisag.pgm.gui verwenden.

Eine Verwendung der Klassen aus dem Package com.cisag.pgm.dialog ist nur dann notwendig, wenn die Elemente aus dem Package com.cisag.pgm.gui erweitert bzw. durch eigene Elemente ergänzt werden sollen.

10.2 User Interface API

Für die Gestaltung der grafischen Oberflächen stellt das ERP-System ein state-of-the-art Programmiermodell zur Verfügung, welches in vielen Bereichen mit dem AWT/Swing-Konzept von Java vergleichbar ist. Die verschiedenen Typen von VisualElements werden jeweils durch eine Java-Klasse repräsentiert und lassen sich über entsprechende Methoden an die jeweiligen Erfordernisse anpassen.

Die Klassen aus dem Package com.cisag.pgm.gui bieten anwendungsspezifische Erweiterungen, die in den ERP-System-Anwendungen benötigt werden. Das sind zum Beispiel:

  • der National Language Support (NLS),
  • die kontextsensitive Hilfe,
  • die Vereinfachung der Benutzung für Data-Descriptions (Beschreibung des GUI-Verhaltens von Eingabe/Ausgabe-Feldern),
  • spezifische Felder wie EntityFields mit zusätzlicher Funktionalität, z. B. Kontextmenü, Drag & Drop,
  • spezifische Erweiterungen für Tabellen und Listen

Weitere Informationen dazu finden Sie im Dokument „Oberflächenentwicklung“.

10.3 Grundlegende Eigenschaften einer Dialog-Anwendung

Eine Dialog-Anwendung benutzt das UI-Framework als Schnittstelle zum Benutzer. Eine Dialog-Anwendung ist abgeleitet von der abstrakten Basis-Klasse CisUiApplication, die grundlegende Funktionalität enthält. Wesentliche Bestandteile einer Dialog-Anwendung sind die Methoden init(), run() und performAction(). Die Methode init() wird vom System zur Initialisierung der Anwendung aufgerufen. Die Methode performAction() dient der Behandlung auftretender Ereignisse, den Actions. Die run()-Methode stellt die Schnittstelle der Anwendung zu anderen Anwendungen und Systemteilen des ERP-Systems dar. Zum Beispiel können über die run()-Methode der Anwendung Startparameter übergeben werden. Eine Dialog-Anwendung hat normalerweise mindestens diese drei Methoden zu implementieren.

Die abstrakte Klasse CisUiApplication implementiert das Interface ActionListener (Methode performAction()). Die von CisUiApplication abgeleitete Anwendung hat die Methode mit einer konkreten Implementierung zur Ereignisbehandlung zu überschreiben. Dasselbe gilt für die init()- und run()-Methode, die ebenfalls mit einer konkreten Implementierung zu überschreiben sind. Die Klasse CisUiApplication ist ein Adapter für eine Anwendungsklasse.

Die nachstehende Abbildung zeigt die Vererbungshierarchie.

10.4 Aufteilung der Anwendungsoberfläche

Die folgende Abbildung zeigt die normale Aufteilung der Oberfläche einer Dialog-Anwendung. Die Funktionen liefern den für den entsprechenden Bereich zuständigen „Visual Element Container“ bzw. dienen zur Einflussnahme auf die Anzeige. Im Anwendungskopf werden der Name der Anwendung und die Bezeichnung des geöffneten Business Objects angezeigt. Zum Setzen des anzuzeigenden Objekt-Namens dient die Methode setObjectDescription(). Der Name der Anwendung wird automatisch aus der Repository-Datenbank ermittelt. Die Standard-Symbolleiste nimmt die Buttons zur Steuerung der Anwendung auf. Im Abfragebereich werden die Eingabefelder zur Auswahl der zu öffnenen Business-Object-Instanz(en) untergebracht. Im Arbeitsbereich werden die Felder mit den geöffneten Daten angezeigt und durch den Benutzer bearbeitet. In der Statuszeile erscheinen die Meldungen für den Benutzer, die von der Anwendung mit der Methode sendMessage()des Message-Managers verschickt wurden. Der Navigationsbereich gliedert sich in mehrere Karteireiter, die von der Anwendung für die Anzeige einer Anwendungswertehilfe, der Historie und der Meldungen verwendet werden.

10.5 Visual Elements

Alle GUI-Elemente sind abgeleitet von der generischen Klasse VisualElement, welche die Grundfunktionalität implementiert. Ein spezielles VisualElement ist der „VisualElementContainer“. Er enthält andere VisualElements, darunter können auch wieder VisualElementContainer sein. Ein VisualElement hat eine systemweit eindeutige ID-Nummer (GUID), die im Konstruktor zugeteilt werden muss, und über die es identifiziert werden kann. Prinzipiell sind alle Eigenschaften des VisualElements (außer die ID) dynamisch bzw. während der Laufzeit variierbar. Die Größe und die Position eines Elements wird vom Layout-Manager des übergeordneten Containers festgelegt. Je nach LayoutManager, werden dabei auch spezielle Eigenschaften (z. B. bevorzugte, minimale bzw. maximale Größe) berücksichtigt.

Die folgende Abbildung zeigt einen VisualElementContainer (VEC), der zwei andere VisualElementContainer enthält. Diese enthalten jeweils zwei VisualElements (VE).

Beispielanwendung: HelloWorld

Die Dialog-Anwendung „HelloWorld“[2] gibt die entsprechende Zeichenkette aus. Die run()-Methode ist hier noch nicht implementiert. Als erstes wird die init()-Methode ausgeführt, in der der Arbeitsbereich initialisiert, ein Label mit der Bezeichnung „Hello World!“ erzeugt und zum VEC des Arbeitsbereiches hinzugefügt wird. Da die Anwendung keine eigenen Ereignisse (Actions) behandelt, ist die Methode performAction() leer.

Der Quelltext der Anwendung:

package com.cisag.app.edu.ui;

 

import com.cisag.pgm.base.CisUiApplication;

import com.cisag.pgm.gui.Action;

import com.cisag.pgm.gui.BorderLayout;

import com.cisag.pgm.gui.Label;

import com.cisag.pgm.gui.View;

 

/**

* Simple application that says “Hello World!”.

*/

 

public class HelloWorld extends CisUiApplication {

 

/** Constant to identify the class version within the byte code. Do not use! */

 

private static final String SEMIRAMIS_CLASS_VERSION = “$Revision: 4.0:ADV130 $”;

public HelloWorld() {

// init CisUiApplication

// automatic close application on exit button

super(CisUiApplication.AUTOMATIC_EXIT);

}

 

/**

* Initializes the application.

*/

 

public void init() {

// get work pane

View pane = getWorkPane();

// init layout

pane.setLayout(new BorderLayout());

// create a label and set text to display

Label label = new Label();

label.setText(“Hello World!”);

// add visual element to work pane

pane.add(label, BorderLayout.CENTER);

}

 

/**

* Action handler.

*/

 

public void performAction(Action action) {

// nothing to do

}

}

10.6 Layout-Manager

Ein Layout-Manager hat die Aufgabe, die Elemente eines VisualElementContainers nach einer bestimmten Strategie anzuordnen. Der zu benutzende Layout-Manager wird mit der Methode setLayout() am Visual ElementContainer gesetzt. Im Folgenden werden die wichtigsten Layout-Manager kurz vorgestellt. Die Beschreibung aller Layout-Manager finden Sie im Dokument „Oberflächenentwicklung“.

10.6.1 Standard-Layout

Mit dem Standard-Layout lassen sich die Elemente eines Containers an übergenordneten Linealen („Guides“) ausrichten. Dies erleichtert nicht nur die mehrspaltige Anordung von Eingabefeldern, sondern führt auch zu container-übergreifenden Fluchtlinien. Spalten lassen sich für jeden Container separat definieren, container-übergreifende Fluchtlinien sind aber nur dann gewähleistet, wenn es nur eine Definition der Spalten gibt und zwar an einem (gemeinsamen) übergeordneten Container. Bei Bedarf können Elemente auch über mehrere Spalten verteilt werden. Für die vertikale Ausrichtung nutzt das Standard-Layout eine Art „Zeilenraster“. Die folgende Abbildung zeigt ein Beispiel für das Standard-Layout.

10.6.2 Border-Layout

Das Border-Layout definiert die fünf Bereiche „Center“, „North“, „East“, „South“ und „West“ (siehe Abbildung). In jedem Bereich kann maximal ein VisualElement[3] platziert werden. In horizontaler Richtung bekommen die Bereiche „West“ und „East“ immer exakt ihre „preferredWidth“ und der Bereich „Center“ den gesamten verbleibenden Rest. Den Bereichen „North“ und „South“ wird die gesamte Breite des Containers zugeteilt. In vertikaler Richtung bekommen die Bereiche „North“ und South“ immer exakt ihre „preferredHeight“ zugeteilt, der verbleibene Rest ist dann die Höhe für die Bereiche „West“, „Center“ und „East“. Wird einem Bereich kein VisualElement zugeordnet, dann kann dieser Platz ggf. durch die anderen Bereiche eingenommen werden. Elemente in den Bereichen „North“ bzw. „South“ bekommen jedoch immer ihre „preferredHeight“ und die Elemente in den Bereichen „West“ bzw. „East“ immer ihre „preferredWidth“. Dies gilt im Übrigen auch dann, wenn der Container gar nicht über die notwendige Höhe und/oder Breite verfügt. In diesem Fall können sich die Elemente gegenseitig überdecken bzw. an der Containergrenze abgeschitten werden und dadurch „unsichtbar“ werden[4].

10.6.3 Box-Layout

Während das Standard-Layout typischerweise zum Platzieren von Feldern in einem Container benutzt wird, dient das Box-Layout eher dazu, ganze Container und andere komplexe Elemente zu platzieren und automatisch anzupassen.

Beim Box-Layout werden die Elemente entweder von links nach rechts (horizontales Box-Layout) oder von oben nach unten (vertikales Box-Layout) angeordnet (siehe Abbildung). Bei der Größenvergabe werden sowohl die bevorzugten Größen als auch die minimalen bzw. maximalen Größen der jeweiligen Elemente berücksichtigt. Auch die Ausrichtung (Alignment) der Elemente untereinander lässt sich beeinflussen. Spezielle Elemente wie „Struts“ oder „Glues“ können benutzt werden, um zusätzliche Abstände zwischen den Elementen einzufügen.

Mithilfe von zwei ineinander geschachtelten Containern (einer mit vertikalem und der andere mit horizontalem Box-Layout) lässt sich beispielsweise auch das Verhalten eines Border-Layouts nachbilden. Durch gezielte Vergabe der minimalen bzw. maximalen Elementgrößen, lässt sich dann auch präzise steuern, wie sich die Elemente verhalten sollen, wenn der Container stark verkleinert wird.

10.7 GUI-Felder

Felder dienen zur Eingabe von Daten wie zum Beispiel Zeichenketten oder Zahlen. Um die unterschiedlichen Datentypen optimal darzustellen, existieren mehrere Feldtypen. Ein Feld besteht aus zwei Teilen, dem Label (Feldbezeichnung) und einem editierbaren Bereich zur Darstellung des Feldwertes. Bei der Eingabe können automatisch Formatierungen vorgenommen werden, um den Wert dem Feldtyp entsprechend darzustellen. Über die entsprechenden get- und set-Methoden kann in der Anwendung der Wert des Feldes abgefragt und gesetzt werden.

Das Anzeigeverhalten eines Feldes wird normalerweise in der Data-Description festgelegt, die einem logischen Datentyp zugeordnet ist. Einem Business-Object-Attribut ist ein logischer Datentyp zugeordnet und somit auch dessen Data-Description. Das Feld benutzt diese Data-Description für seine Visualisierung.

Häufig benutzte Feldtypen sind:

  • BooleanField, CheckBox
  • ComboBox und ValueSetField
  • IntegerField, ShortField, DecimalField
  • TextField, ExtendedTextField
  • CisDateField
  • Entity-Felder für Business Object-Entitys

10.8 Ereignissteuerung

Die Benachrichtigung der Anwendung bei aufgetretenen Ereignissen (z. B. Klicken auf Button) erfolgt über Nachrichten, genannt Actions. An der Action werden die Beobachter mit der Methode addActionListener() registriert. Diese implementieren das Interface ActionListener (Methode performAction()) und werden von der Action bei deren Auslösung benachrichtigt. Das GUI-Element löst die Action aus, indem es die Methode fireAction() an der Action aufruft. Die Action benachrichtigt alle bei ihr registrierten Beobachter durch Aufruf der Methode performAction(). Dabei übergibt sich die Action selbst als Parameter. Der Action Listener kann nun den Typ der Action feststellen und diese geeignet behandeln.

Die Signaturen der Methoden sind:

public void addActionListener(ActionListener l);

public void performAction(Action a);

Die Klassen Action und ActionListener gehören zu dem Package com.cisag.pgm.gui.

10.9 Beispiel „BookMaintenance“

In den nachfolgenden Kapiteln werden die Grundkonzepte der Entwicklung von Dialog-Anwendungen anhand der Beispielanwendung „BookMaintenance“ im Framework „Education“ eingeführt. Sie dient zum Verwalten von Büchern, denen das Business Entity „Book“ aus dem Namensraum com.cisag.app.edu.obj zugrunde liegt. Für eine voll funktionsfähige dialogbasierte Bücheranwendung ist die Implementierung von zumindest vier Klassen notwendig:

  • BookField
  • BookMaintenance
  • BookEntity
  • BookValidation

Alle diese Klassen besitzen Basisklassen (siehe Klassendiagramm), welche bereits die jeweils notwendigen Grundfunktionalitäten implementieren. Dadurch ist für den Anwendungsprogrammierer möglich, relativ rasch eine dialogbasierte Anwendung zu implementieren. So ist beispielsweise in der BookMaintenance nur noch notwendig, die konkrete Oberflächengestaltung (z. B. Positionierung der GUI-Felder) und den Datenabgleich zwischen dem Business-Object-Modell einerseits und GUI-Feldern andererseits festzulegen. Die Grundfunktionalitäten wie Erfassen oder Löschen eines neuen Buches werden bereits in der Superklasse „SingleObjectMaintenance“ implementiert.

Im folgenden Klassendiagramm sind alle Book-Klassen, welche der BookMaintenance-Anwendung zugrunde liegen, und ihre Vererbungsbeziehungen dargestellt.

In den nachfolgenden Kapiteln werden alle wichtigen Aspekte, die für die Entwicklung von einfachen Stammdatenanwendungen notwendig sind, erläutert und Beispiele aus der Anwendung „BookMaintenance“ gegeben.

Dabei werden im Wesentlichen folgende Programmiertechniken behandelt:

  • GUI-Elemente (z. B. Eingabefelder) und deren Positionierung
  • Datenabgleich zwischen GUI-Elementen und BO-Feldern
  • Behandlung von Ereignissen (Actions),
  • Öffnen und Speichern von Business-Object-Instanzen,
  • Modellkonzept einer Anwendung,
  • Prüfung von Business-Object-Instanzen, Berichtigungen
  • Ausgabe von Meldungen und Anzeigen der Meldungsecken an betroffenen GUI-Elementen,
  • Anwendungs- und Feldwertehilfe,
  • Führen der Historie und Verlauf
  • National Language Support (NLS)

Anschließend wird anhand der Beispielanwendungen BookTableMaintenance und BookListInquiry die Verwendung von Tabellen und Listen eingeführt.

Das Ziel dieser Beispielanwendung ist, sich mit der Programmierung von Dialogen zur Führung von Business Entitys im ERP-System vertraut zu machen. Die folgende Abbildung zeigt die GUI-Oberfläche der Anwendung „BookMaintenance“ und die zugehörige Suche (Anwendungswertehilfe) im Navigationsbereich.

10.10 Modell-Konzept

Ein gutes Design einer Dialog-Anwendung ist, wenn die auf der Oberfläche dargestellten Daten von den Daten in der Anwendung entkoppelt sind. Intern werden die Daten in einer für die Anwendung günstigen Struktur gehalten, dem so genannten Modell. Nach Änderung der Daten im Modell erfolgt abschließend die Übertragung der Daten in die GUI-Element,e z. B. die Felder) des User Interfaces. Dazu wird die Methode dataToUi() benutzt. Nach bestimmten Benutzerinteraktionen (z. B. Speichern) erfolgt die Datenübernahme aus dem User Interface in das Modell der Anwendung zur Weiterverarbeitung. Dazu dient die Methode dataFromUi().

10.11 Ablauf einer Dialog-Anwendung

Wenn eine Dialog-Anwendung geöffnet wird, dann ruft das System immer als erstes die Methode init() der erzeugten Anwendungsinstanz auf. In dieser Methode erfolgt die Initialisierung des Anwendungsmodells und der GUI-Elemente der Oberfläche.

Als zweites ruft das System die run()-Methode der Anwendung auf. Dabei werden eine Action-ID und eine Parameterliste übergeben. Bei der Action-ID „INIT_DEFAULT“ wurde die Anwendung ohne Parameter gestartet. In diesem Fall sollte die Anwendung ihre Standardbelegungen im Modell setzen. Wurde die Action-ID „INIT_LOAD“ übergeben, dann existieren Startparameter in der Parameterliste. Diese müssen von der Anwendung ausgewertet und ins Modell übertragen werden. Abschließend erfolgt der Transfer der Daten aus dem Modell ins User Interface über die Methode dataToUi(), um diese anzuzeigen.

Eine Dialog-Anwendung ist als „Action Listener“ standardmäßig bei einer Reihe von Actions registriert. Im Wesentlichen sind das die zu den Buttons in der Standard-Symbolleiste korrespondierenden Actions. Zur Ereignisbehandlung wird die Methode performAction() der Anwendung aufgerufen, dabei wird die aufgetretende Action als Parameter übergeben. Nach Feststellung des Typs der Action wird die zugehörige Ereignisbehandlungsroutine ausgeführt. Wenn benötigt (der Benutzer hat zum Beispiel den Speichern-Button gedrückt), werden die Daten vom User Interface in das Modell der Anwendung übertragen (dataFromUi()). Anschließend wird die entsprechende Funktionalität (z.B. Öffnen, Speichern, Löschen) ausgeführt. Abschließend werden die Daten aus dem Modell wieder ins User Interface übertragen (dataToUi()). Die performAction()-Methode ist also die zentrale Methode in der Anwendung, die durch die Ereignisbehandlung immer wieder ausgeführt wird, bis die Anwendung beendet wird.

10.12 Generische Business-Object-Methode get_instanceString()

Die vom System generierte generische Methode get_instanceString() eines Business-Objects liefert eine für den Benutzer aussagekräftige Zeichenkette, die aus dem Werten der Business-Key-Attribute und einem eventuell vorhandenen Attribut mit dem Namen „description“ zusammengesetzt ist. Es wird die toString()-Methode benutzt, um den Wert in eine Zeichenkette zu konvertieren, falls der Wert kein String war. Diese Zeichenkette wird in der Titelzeile der Anwendung zur Anzeige des geöffneten Business Entitys, für die Beschriftung von Links, im Verlauf, usw. benutzt. Soll die Methode einen anders zusammengesetzten String liefern, dann muss sie überschrieben werden.

10.13 Anwendungsmodus und Sicherheitsabfragen

Die Hilfsklasse „ConfirmationDialog“[5] dient zum Erzeugen von Sicherheitsabfragen, damit Benutzereinganben nicht versehentlich verlorgen gehen. Das könnte passieren, wenn Aktionen ausgeführt werden sollen, durch welche die Änderungen verloren gehen (z. B. Beenden, Öffnen/Aktualisieren). Wann eine Sicherheitsabfrage notwendig ist, wird von der Klasse „ConfirmationDialog“ anhand des Anzeigemodus der Anwendung entschieden, den diese zu führen hat. Der Anzeigemodus kann die drei Zustände Anzeigen, Bearbeiten und Neu annehmen.

Diese haben die folgende Bedeutung:

  • Modus Anzeigen:

Die Daten im Modell und der Kopie des Modells stimmen überein. Vor dem Verwerfen der Daten aus dem Modell ist keine Sicherheitsabfrage notwendig, da keine Änderungen durch den Benutzer vorliegen. Das Setzen dieses Modus erfolgt mit dem Aufruf setApplicationMode(DISPLAY_MODE).

  • Modus Bearbeiten:

Die Daten im Modell stimmen nicht mit den Daten der Kopie des Modells überein. Bevor die Daten im Modell verworfen werden, ist eine Sicherheitsabfrage notwendig, da Änderungen durch den Benutzer vorliegen. Das Setzen dieses Modus erfolgt mit dem Aufruf setApplicationMode(UPDATE_MODE).

  • Modus Neu:

Die Daten im Modell wurden vom Benutzer neu eingegeben. Bevor diese Daten verworfen werden, ist eine Sicherheitsabfrage notwendig. Das Setzen dieses Modus erfolgt mit dem Aufruf setApplicationMode(CREATE_MODE).

Zur Realisierung des gewünschten Verhaltens wird eine auszuführende Basisaktion (z. B. Löschen) in der performAction()-Methode in zwei Schritte aufgeteilt. Ist eine Aktion zu behandeln, wird zuerst geprüft, ob eine Sicherheitsabfrage nötig ist. Wenn ja, erfolgt die Anzeige eines Warndialoges, über den der Benutzer die Ausführung der anstehenden Aktion bestätigen oder ablehnen kann. Das Öffnen des Dialogs muss die letzte Aktion im Roundtrip sein, danach darf kein Code mehr ausgeführt werden. Aus diesem Grund steht die Abfolge dataFromUi()/dataToUi() vor dem eigentlichen Aufruf. Damit ist auch gewährleistet, dass der Benutzer die korrekte Objektbeschreibung im Anwendungskopf sieht. Hat der Benutzer die Aktion bestätigt, wird die performAction()-Methode der Anwendung erneut ausgeführt. Als Parameter wird jetzt die bestätigte Variante der Aktion übergeben. Die Action-ID beginnt in diesem Fall mit dem Präfix „CNF_“. Der folgende Quelltextauszug zeigt die Behandlung von Action.DELETE als Beispiel:

 

public void performAction(Action action){

switch(action.getId()){

case Action.DELETE:

mm.getProgramMessageQueue().reset();

dataFromUi();

dataToUi(); // Complete roundtrip

ConfirmationDialog.confirmDelete(this);

return; // No more code

case Action.CNF_DELETE:

if (delete()) removeRecordedAction();

break;

}

10.14 Erweitertes Modell-Konzept

Der Forderung nach der Realisierung von optimistischen Sperren trägt das erweiterte Modellkonzept Rechnung. Dieses Modell arbeitet zusätzlich mit einer Kopie, die zum Feststellen von Änderungen benötigt wird. Die prinzipiellen Abläufe bei den Basisaktionen sind wie folgt:

Create

Die Daten werden aus dem „User Interface“ ins Modell übertragen (siehe 1. in folgender Abbildung). Gegebenenfalls wird in Abhängigkeit vom Anwendungsmodus der Warndialog angezeigt. Wurde dieser vom Benutzer bestätigt, überträgt die Anwendung die Standardeinstellung ins Modell (siehe 2.) und überträgt die Daten ins User Interface (siehe 3.).

Load

Die Daten werden aus dem „User Interface“ ins Modell übertragen (siehe 1. in folgender Abbildung). Gegebenenfalls wird in Abhängigkeit vom Anwendungsmodus der Warndialog angezeigt. Wurde dieser vom Benutzer bestätigt, liest die Anwendung das Business Object von der Datenbank (siehe 2.). Die Daten werden aus der nicht transienten Business-Object-Instanz ins Modell übertragen (siehe 3.). Vom Modell wird eine Kopie aufgehoben (siehe 4.), um später zwischenzeitliche Änderungen auf der Datenbank erkennen zu können. Der Vergleich kann entweder über einen Inhaltsvergleich oder über den Zeitstempel der letzten Änderung erfolgen. Abschließend werden die Daten zum „User Interface“ übertragen (siehe 5.).

Update

Die Daten werden aus dem „User Interface“ ins Modell übertragen (siehe 1. in folgender Abbildung). Eine Transaktion wird geöffnet und das Business Object zum Ändern mit dem Flag READ_UPDATE oder READ_WRITE gelesen (siehe 2.). Nun wird die gelesene Instanz mit der früher gemachten Kopie verglichen (siehe 3.). Sind sie unterschiedlich, dann wurde die Business-Object-Instanz nach dem Lesen zur Bearbeitung zwischenzeitlich von einem anderen Benutzer geändert. In diesem Fall darf nicht gespeichert werden, um die Änderungen vom anderen Benutzer nicht zu verlieren. Bei Gleichheit erfolgt die Datenübernahme aus dem Modell in die nicht transiente Business-Object-Instanz (siehe 4.) und das Registrieren der Instanz zum Speichern (siehe 5.). Mit dem Beenden der Transaktion werden die Änderungen auf der Datenbank persistent gemacht. Die Business-Object-Instanz wird danach erneut gelesen (siehe 6.), um die Werte der vom Persistenzdienst automatisch geführten Attribute (z. B. die Update Information) auch in der Anwendung aktuell zu haben. Die Daten werden aus der nicht transienten Business-Object-Instanz ins Modell übertragen (siehe 7.). Vom Modell wird eine Kopie aufgehoben (siehe 8.), um später zwischenzeitliche Änderungen auf der Datenbank erkennen zu können. Abschließend werden die Daten zum User Interface übertragen (siehe 9.).

Delete

Die Daten werden aus dem „User Interface“ ins Modell übertragen  (siehe 1. in folgender Abbildung). Gegebenenfalls wird in Abhängigkeit vom Anwendungsmodus der Warndialog angezeigt. Wurde dieser vom Benutzer bestätigt, wird eine Transaktion geöffnet und das Business Object zum Löschen mit dem Flag READ_UPDATE geöffnet (siehe 2.). Nun wird die gelesene Instanz mit der früher gemachten Kopie verglichen (siehe 3.). Sind sie unterschiedlich, so wurde die Business-Object-Instanz nach dem Lesen zur Bearbeitung zwischenzeitlich von einem anderen Benutzer geändert. In diesem Fall darf nicht gelöscht werden, um die Änderungen vom anderen Benutzer nicht zu verlieren.

Bei Gleichheit wird die Business-Object-Instanz zum Löschen registriert (siehe 4.) und bei Beendigung der Transaktion aus der Datenbank gelöscht. Abschließend werden die Daten aus dem Modell in die Kopie übertragen (siehe 5.), damit die Kopie den gleichen Stand hat, und wieder ans „User Interface“ übertragen (siehe 6.).

10.15 Implementierung in der „SingleObjectMaintenance“

Die „SingleObjectMaintenance“ implementiert in der Methode performAction das Verhalten der Buttons in der Standard-Symbolleiste, die dem Benutzer die Funktionalität zum Erfassen, Anzeigen, Ändern und Löschen von SingleObjectEntity-Instanzen (zum Beispiel Instanzen des Business Entity „Book“) bereitstellen. Die Implementierung der zugehörigen Methoden ist angedeutet. Dazu hat sie die entsprechenden Ereignisse der Buttons in der Standard-Symbolleiste zu behandeln und muss den Persistenzdienst benutzen. Die Anwendung benutzt das vorgestellte Modellkonzept, also die Trennung von den intern benutzten Daten und den Daten im „User Interface“. Sie implementiert die get_instanceString()-Methode, um das geöffnete Buch in der Titelzeile anzeigen zu können.

Die benötigten Manager (hier Transaktions- und Objektmanager) holt sich die Anwendung vom Environment. In der init()-Methode erfolgt die Initialisierung des Modells (createModel()). Da in der Anwendung nur ein Business Object geführt wird, ist das Modell einfach aufgebaut. Es besteht aus einer transienten, nicht persistenten Instanz des Business Objects, die nicht transaktions- oder datenbankabhängig ist. Die applyDefaults()-Methode belegt das Modell mit Vorschlagswerten, der existierende Inhalt wird überschrieben. Mit der Methode dataToUi() werden die Daten im Modell zum „User Interface“ übertragen, in dem die GUI-Felder mit den zugehörigen Attributwerten gefüllt werden. Mit der von der CisUIApplication geerbten Methode setObjectDescription() wird im Anwendungskopf die Bezeichnung der im Modell enthaltenen Business-Object-Instanz gesetzt, welche die Anwendungsmethode getInstanceString() liefert. In der Methode dataFromUi() werden die Daten aus den GUI-Feldern in die entsprechenden Attribute des Modells übertragen. In der performAction()-Methode erfolgt die Behandlung der vom Benutzer ausgelösten Actions in der Standard-Symbolleiste. Generell wird am Ende der Methode dataToUi() aufgerufen, um das „User Interface“ zu aktualisieren.

Action.CREATE:

Drückt der Benutzer den Neu-Button in der Standard-Symbolleiste, um ein neue Instanz zu erfassen, dann wird eine Action mit der Action-ID ausgelöst. Die aufgerufene create()-Methode weist dem Modell die Standardeinstellungen zu, dabei wird eine neue GUID als Primärschlüsselwert für die zu erfassende Instanz gezogen.

Action.DUPLICATE:

Drückt der Benutzer den Duplizieren-Button in der Standard-Symbolleiste, um ein neue Instanz durch Duplizieren einer bereits geöffneten Instanz zu erfassen, dann wird eine Action mit dieser Action-ID ausgelöst. Die Daten aus dem „User Interface“„User Interface“ werden ins Modell übernommen und die Methode duplicate() aufgerufen. Es wird für die zu erfassende Instanz eine neue GUID als Primärschlüsselwert gezogen und der Business Key gelöscht. Letzterer muss vom Benutzer neu definiert werden.

Action.LOAD:

Drückt der Benutzer den Button „Aktualisieren“ in der Standard-Symbolleiste, um eine Instanz zum eingegebenen Business Key zu öffnen, dann wird eine Action mit dieser Action-ID ausgelöst. Die Daten aus dem „User Interface“ werden ins Modell übernommen und die Methode load() aufgerufen. Aus dem eingegebenen Business Key wird der technische Schlüssel gebildet und die Business-Object-Instanz über die Methode getObject() des Objektmanagers geöffnet. Es muss ein Class-Cast auf die konkrete Klasse erfolgen, da der Objektmanager immer Objekte der allgemeinen Klasse CisObject liefert. Mit der copyTo()-Methode wird der Inhalt der Attribute in das Modell kopiert.

Action.UPDATE:

Drückt der Benutzer den Button „Speichern“ in der Standard-Symbolleiste, um eine erfasste Instanz zu speichern, wird eine Action mit dieser Action-ID ausgelöst. Die Daten aus dem „User Interface“ werden ins Modell übernommen und die Methode update() aufgerufen. Über das Persistenzflag des Modells wird entschieden, ob es sich um eine Neuanlage oder Änderung handelt. Dementsprechend wird dann die Methode createInternal() oder updateInternal() aufgerufen. Dort wird eine Transaktion über den Transaktionsmanager geöffnet. Mit getObject() wird über den aus dem Business Key generierten technischen Schlüssel die Instanz unter Angabe des Flags READ_WRITE bzw. READ_UPDATE geöffnet. Anschließend werden die Attribute der transienten Instanz des Modells in die nicht transiente Instanz der Transaktion mit copyTo() kopiert. Mit putObject() wird die Business-Object-Instanz zum Speichern registriert und mit dem commit() der Transaktion auf der Datenbank persistent gemacht. Tritt eine Exception auf, wird die Transaktion mit rollback() explizit abgebrochen und die Exception an die Message-Queue der Anwendung gesendet. Abschließend wird die gespeicherte Business-Object-Instanz neu geöffnet und ins Modell kopiert. Damit sind eventuelle vom Persistenzdienst beim Speichern automatisch gesetzte Attribute im Modell aktuell. Das betrifft zum Beispiel die Updateinformation.

Action.DELETE:

Drückt der Benutzer den Button „Löschen“ in der Standard-Symbolleiste, um eine Instanz zu löschen, wird eine Action mit dieser Action-ID ausgelöst. Die Daten aus dem „User Interface“ werden ins Modell übernommen und die Methode delete() aufgerufen. Die Instanz wird mit getObject() unter Angabe des Flags READ_UPDATE geöffnet. Wurde eine Instanz gefunden, wird sie mit deleteObject() zum Löschen registriert und beim commit() der Transaktion in der Datenbank gelöscht. Das Modell wird nicht geleert, um den Benutzer bei einer versehentlichen Löschung Gelegenheit zu geben, die Daten zu retten. Das Modell wird als nicht persistent markiert.

10.16 taskSwitch()-Methode

Die Implementierung der Methode taskSwitch() durch die Anwendung ist unbedingt erforderlich. Sie dient dem System zum Ermitteln des Anwendungsmodus einer Anwendung (Anzeigen/Neu/Bearbeiten) und zum Aktualisieren der Anzeige beim Wechsel der Organisation in der Standard-Symbolleiste. Beispielsweise schließt das System automatisch Anwendungen, die im Hintergrund sind, wenn es für nötig gehalten wird, z. B. bei Speichermangel. Damit durch das Beenden von Anwendungen keine Benutzereingaben verloren gehen, dürfen nur Anwendungen, die sich im Modus „Anzeigen“ befinden, geschlossen werden. Zur Ermittlung des Modus wird vom System die taskSwitch()-Methode der Anwendung aufgerufen, die den Anwendungsmodus setzt. Innerhalb dieser Methode sind nur wenige ausgewählte Operationen erlaubt, wie:

  • das Einlesen der GUI-Felder über dataFromUi(),
  • das Setzen der GUI-Felder über dataToUi(),
  • das Setzen der Anwendungstitelzeile über setObjectDescription(),
  • das Setzen des Anwendungsmodus über setApplicationMode().

Der folgende Quellcode zeigt die Implementierung der Methode taskSwitch in der SingleObjectMaintenance:

public void taskSwitch() {

dataFromUi();

dataToUi();

}

Auch eine Anwendung, die nur Daten anzeigt, muss die Methode taskSwitch() implementieren, wenn sie Daten in folgenden Feldern anzeigt:

  • CisDateField
  • TimeStampField
  • CisPointInTimeField/PointInTimeField
  • DomesticAmountField

Dies ist notwendig, damit beim Wechsel der Organisation in der Workflow-Symbolleiste die Felder neu berechnen, welche Werte und welche zusätzlichen Daten (z. B. die Zeitzone) sie anzeigen müssen.

10.17 taskActivate()-Methode

Die Methode taskActivate() wird vom System aufgerufen, wenn eine inaktive Anwendung zur aktiven Anwendung wird. Sie dient, wie die Methode taskSwitch(), dem Aktualisieren der Feldinhalte für die Felder. Hiermit lässt sich beispielsweise ein automatisches Nachladen beim Wechsel zur Anwendung realisieren, wenn eine andere Anwendung inzwischen die in der Anwendung geöffneten Daten verändert hat. Die Standardimplementierung in der Klasse CisUiApplication ruft die Methode taskSwitch() auf. Falls eine Unterscheidung für das Aktivieren benötigt wird, kann die Methode taskActivate() überschrieben werden.

10.18 AUTOMATIC_ENTER und AUTOMATIC_EXIT

Ist das Standardverhalten bezüglich der ENTER-Taste und des Schließens der Anwendung gewünscht, wird im Konstruktor der Anwendung der Parent-Konstruktor der CisUIApplication mit den entsprechenden Flags aufgerufen:

super(CisUiApplication.AUTOMATIC_EXIT |

CisUiApplication.AUTOMATIC_ENTER);

Normalerweise sollte das eine Dialog-Anwendung immer tun.

Betätigt der Benutzer die ENTER-Taste, läuft folgendes Standardverhalten der Anwendung ab: Das System ruft die taskSwitch()-Methode der Anwendung zur Bestimmung des Anwendungsmodus auf. Beim Modus „Anzeigen“ wird die Methode performAction mit der Action-ID Action.LOAD aufgerufen, ansonsten mit der Action-ID Action.VALIDATE.

Beim Beenden der Anwendung passiert Folgendes: Das System ruft die taskSwitch()-Methode der Anwendung zur Bestimmung des Anwendungsmodus auf. Beim Modus „Anzeigen“ wird die Anwendung geschlossen, ansonsten der Bestätigungsdialog angezeigt.

10.19 Meldungen

Eine Dialog-Anwendung benutzt Meldungen, um den Benutzer über bestimmte Ereignisse oder Zustände zu informieren. Diese werden in der Statuszeile und im Informationskarteireiter angezeigt. Es ist möglich, den Bezug einer Meldung zu einem GUI-Element (z. B. einem Eingabefeld) durch eine Markierung des GUI-Elementes mit einer entsprechenden Meldungsecke zu kennzeichnen. Zur Verwaltung der von der Anwendung gesendeten Meldungen dient die Meldungswarteschlange (Message Queue), die vom Message-Manager zur Verfügung gestellt wird. Den Message-Manager kann die Anwendung über das Environment abfragen:

env = CisEnvironment.getInstance();

CisMessageManager mm = env.getMessageManager();

Die Anwendung kann die Meldungswarteschlange mit folgender Anweisung komplett leeren:

mm.getProgramMessageQueue().clear();

Das Zurücksetzen der Message Queue, erfolgt über die Anweisung:

mm.getProgramMessageQueue().reset();

Dabei bleiben durch den Benutzer bestätigte Warnungen erhalten, alle anderen Einträge werden gelöscht.

Im Anwendungsprogramm wird die Meldung über den Namen der zugeordneten Meldungsklasse und eine Nummer identifiziert.[6] Zum Eintragen einer Meldung in die Meldungswarteschlange dient die Anweisung:

mm.sendMessage(String className, int number);

Zusätzlich kann eine Meldung Identifikationen beinhalten, mit denen man sie auf ein bestimmtes Feld oder ein bestimmtes Objekt bezieht. Diese Identifikationen sind:

  • Message-Group

Dies ist zumeist das Objekt und entspricht oft einer Zeile in Listen, Tabellen oder Positionseditoren.

  • Message-Path

Dies ist zumeist die Rubrik oder die Ansicht.

  • Message-ID

Dies ist zumeist das Feld.

Beim Aufruf von mm.sendMessage() wird die Meldung nur dann in die Meldungswarteschlange aufgenommen, wenn nicht bereits eine Meldung mit der gleichen Meldungsklasse, Meldungsnummer usw. darin enthalten ist. Hierdurch wird beispielsweise das Bestätigen von Meldungen des Typs „Warnung“ realisiert. Diese können über eine Checkbox vom Benutzer bestätigt werden und verbleiben in der Meldungswarteschlange. Wenn die Warnung mit der gleichen Identifikation erneut (d. h. unbestätigt) gesendet wird, dann wird sie nicht mehr in die Meldungswarteschlange aufgenommen.

Zur individuellen Anpassung des Meldungs- bzw. Langtextes ist die Angabe von Platzhaltern möglich. Ein Platzhalter wird durch eine Ziffer in geschweiften Klammern repräsentiert. Es kann maximal zehn verschiedene Platzhalter in einer Meldung geben. Die Werte können durch die Angabe entsprechender Parameter bei der Methode sendMessage() mit Werten belegt werden:

mm.sendMessage(String className, int number,

String parm0,…, String parm9);

Ein Beispiel für Meldungstext mit Platzhalter ist: „Partneridentifikation {0} ist schon vorhanden.“

Bei der Benutzung der Platzhalter ist darauf zu achten, dass diese nur für Daten, wie z. B. die Partneridentifiaktion, vorgesehen sind. Der eigentliche Meldungstext wird immer in der Repository-Datenbank erfasst. Damit wird gewährleistet, dass die Meldungstexte übersetzbar bleiben und in der vom Benutzer gewählten Anzeigesprache angezeigt werden. Deshalb müssen auch bei geringen Abweichungen im Meldungstext mehrere Meldungen erfasst werden.

Das Anzeigen der Meldungen in der Meldungswarteschlange an der Oberfläche erfolgt automatisch vom System. In der Statuszeile wird nur die Meldung mit dem höchsten Schweregrad angezeigt. Alle gesendeten Meldungen der Anwendung sind in der „Message-Pane“ sichtbar. Da der verfügbare Platz beschränkt ist, sollte der Text der Meldung nicht zu lang gewählt werden. Für weitere Erklärungen bzw. Hinweise steht der Langtext zur Verfügung, der, wenn vorhanden, aufklappbar ist.

Für eine Meldung kann ein Bezug zu einem GUI-Element der Oberfläche festgelegt werden. Dazu wird dem GUI-Element eine Message-ID zugeordnet. Bevor eine Meldung mit sendMessage() gesendet wird, muss das Setzen der entsprechenden Message-ID erfolgen. Am GUI-Element wird dann die Meldungsecke in der entsprechenden Farbe angezeigt. Bei Meldungen allgemeinerer Art (z. B. bei erfolgreicher Speicherung) ohne zuordbarem GUI-Element wird die Message-ID „0“ verwendet. Für Meldungen zu Prüfungen, die keinen Bezug zu einem GUI-Element haben, wird trotzdem eine eigene Message-ID gesetzt. So kann die Meldung später bei Bedarf einem GUI-Element zugeordnet werden.

Die Message-ID wird mit der folgenden Anweisung gesetzt:

mm.setProgramMessageId(number);

Zur Registrierung einer Message-ID an einem GUI-Element dient die Methode registerMessage() des Visual Elements. Der folgende Quelltextauszug aus der „BookMaintenance“ gibt ein Beispiel:

//Registrieren der Message-ID am GUI-Feld

code.registerMessage(BookValidation.CODE);

Zum Absetzen einer Meldung dienen die Methoden setProgramMessageId() und sendMessage(). Der folgende Quelltextauszug aus der BookValidation gibt ein Beispiel:

//Setzen des Bezuges für folgende Meldungen zum GUI-Feld

mm.setProgramMessageId(BookValidation.CODE);

//Senden der Meldung

mm.sendMessage(“EDU”, 19, book.getCode());

Die Abfrage, ob Fehlermeldungen bzw. unbestätigte Warnungen in der Meldungswarteschlange stehen, erfolgt über die Methode requiresAttention() am Message-Manager. Diese Methode kann in der Prüfklasse für ein Business Object benutzt werden, um den Erfolg der Prüfung zurückzugeben. Wurden bei der Prüfung Meldungen vom Typ „Fehler“ oder unbestätigte Meldungen vom Typ „Warnung“ in die Meldungswarteschlange eingetragen, dann können die vom Benutzer eingegebenen Daten nicht fehlerfrei sein. Die Methode liefert in diesem Fall „true“, ansonsten „false“. Die Anweisung zum Abfragen des Status der Meldungswarteschlange ist:

boolean status= mm.getProgramMessageQueue().requiresAttention();

Meldungen und Exceptions

Im Programm auftretende Exceptions sollten nicht in eine Meldung umgewandelt werden. Meldungen der Art „Beim Öffnen ist ein schwerer unbehandelter Fehler aufgetreten“ sind nicht zulässig, da sie die Fehlersuche erschweren und im Falle von I/O-Exceptions und Kernel-Exceptions falsch sind. Beispielsweise ist das Auftreten einer Exception beim Speichern durch den Sperrdienst keine Ausnahme, sondern nur ein Fehler („… durch Benutzer {0} gesperrt.“). Die Verwendung einer eigenen Meldung würde dieses Verhalten umgehen. Auftretende Exceptions werden mit mm.sendMessage(ex) an die Meldungswarteschlange gesendet. Die Meldungswarteschlange wertet den Exception-Typ aus und wandelt die Exception je nach gefundenen Typ in eine passende Meldung um. So wird beispielsweise eine Locking-Exception in eine Fehlermeldung „… durch Benutzer {0} gesperrt.“ konvertiert oder eine FileNotFoundException in die Meldung „Datei {0} wurde nicht gefunden.“. Die Meldungswarteschlange protokolliert bestimmte Exceptions automatisch im Systemprotokoll, damit Fehler besser nachvollziehbar sind. Bei kritischen Fehlern vom Typ java.lang.Error wird der SAS automatisch beendet, da die Datenintegrität nicht mehr gewährleistet und der SAS in einem inkonsistenten Zustand ist.

Eine Anwendung sollte die Exception senden (Was ist passiert?) und anschließend eine eigene Meldung mit den Informationen über den Programmstatus, das aktuell bearbeitete Objekt und die Reaktion auf den Fehler senden.

10.20 Prüfklassen

Jedes Business Entity sollte eine Prüfklasse haben, die sicherstellt, dass die Integritätsbedingungen für die Attributwerte der zugehörigen Business-Object-Instanzen erfüllt werden und der Benutzer autorisiert ist, die Änderungen vorzunehmen. Für den Namen einer Prüfklasse gilt die Namenskonvention Business-Object-Name + „Validation“. Für das Beispiel-Business-Object „Book“ heißt die Prüfklasse „BookValidation“[7].

Normalerweise stellt eine Prüfklasse für jede Basisaktion wie Erfassen, Ändern, Löschen eine Prüfmethode bereit, die für eine Instanz des Business Objects feststellen kann, ob diese gültig ist oder nicht.

Solche Prüfungen können sein:

  • Prüfung einer Business-Object-Instanz beim Erfassen, Ändern oder Löschen (Methoden validateCreate(), validateUpdate(), validateDelete())
  • Prüfung des fachlichen Schlüssels einer Business-Object-Instanz (ist er unbenutzt, wurde er zwischenzeitlich geändert bzw. gelöscht usw.)
  • wurde das Business Entity zwischenzeitlich geändert (optimistisches Sperren)
  • Prüfung von Primär- oder Fremdschlüsselattributen (isDefined(),isSpecified())
  • Attributprüfungen (isAttributeSpecified(), weitere fachliche Bedingungen)
  • Verwendungsprüfung (existieren noch Referenzen auf diese Instanz in anderen Business-Object-Instanzen, zum Beispiel beim Löschen?)

Die Message-IDs sollten in der Prüfklasse definiert sein, da diese vor dem Senden der entprechenden Meldungen nach erfolgter Prüfung zu setzen sind. Die Prüfklasse soll unabhängig von einer bestimmten Anwendung sein, sie stellt die Message-IDs für andere Anwendungen zur Verfügung. Die verwendeten Message-IDs orientieren sich an den Attributen und nicht an den UI-Feldern.

Die Klasse com.cisag.pgm.util.RepositoryValidation stellt etliche Basisfunktionen zum Prüfen der ERP-System-Datentypen und darauf basierenden Business-Object-Attributen bereit. Diese können verwendet werden, um Werte bestimmter Datentypen auf Existenz, Richtigkeit zu überprüfen. Es werden automatisch generische Meldungen an die Meldungswarteschlange gesendet, sodass man dafür keine eigenen Meldungen oder nur ergänzende Meldungen in der Anwendung erfassen muss.

Design von Prüfklassen

Eine Prüfklasse ist normalerweise sessionbezogen zustandslos (session stateless), d. h. sie merkt sich keine instanzbezogenen Daten. Sessionbezogen zustandslose Klassen dürfen sich nur Objekte merken, die nur einmal pro Session auftreten, in diesem Fall sind das die Manager. Eine Prüfklasse stellt nur die Prüflogik bereit. Dadurch können mehrere Anwendungen pro Session ein und dieselbe Prüfklassen-Instanz verwenden, was Ressourcen spart.

Es gibt Fälle, in denen eine Prüfklasse auch private Variablen hat, da instanzbezogene Daten gespeichert werden müssen. Eine solche Prüfklassen-Instanz kann nicht von anderen Anwendungen wieder verwendet werden, es wird eine neue Instanz benötigt.

public static BookValidation getInstance() {

return (BookValidation)

CisFactory.getInstance(BookValidation.class);

}

Prüfklassen sollten unabhängig von einem konkreten Anwendungstyp sein, sie implementieren nur Prüflogik für ein Business Entity und nicht Schnittstellen zum Benutzer (kanalübergreifende Logik). Das bedeutet, dass die implementierte Logik in Dialog-, Batch- und anderen Anwendungen zur Verfügung steht. Deshalb liegen solche Klassen in dem Unternamensraum „log“.

Berechtigungsprüfung auf Benutzerebene

Für Berechtigungsprüfungen steht der Systemmanager zur Verfügung, der vom Environment geholt werden kann. Für die Basisaktionen Create, Load, Update, Delete kann für eine Business-Object-Instanz überprüft werden, ob der Benutzer das Recht hat, die gewählte Aktion auszuführen. Der folgende Quelltextauszug zeigt die Überprüfung der Berechtigung für das Löschen einer Business-Object-Instanz mit der Methode isAuthorized() des Systemmanagers. Diese liefert „true“, wenn der angemeldete Benutzer die Berechtigung für die angegebene Aktion besitzt, sonst „false“. Hat der Benutzer das Recht nicht, wird eine entsprechende Meldung ausgegeben.

private CisEnvironment  env = CisEnvironment.getInstance();

private CisSystemManager sm = env.getSystemManager();

if (!sm.isAuthorized(CisSystemManager.DELETE, obj)) {

mm.sendMessage(“APP”, 18);

return false;

}

Implementierung der „BookValidation“

Die Klasse „BookValidation“ ist von der Klasse SingleObjectValidation abgeleitet und implementiert jene Prüflogik, die spezifisch für das Business Entity „Book“ ist, wie zum Beispiel die Anforderung, dass bei jedem entliehenen Buch ein gültiger Partner als Entleiher eingetragen sein muss.

Die BookValidation definiert zunächst numerische Konstanten für die relevanten Attribute des zugrunde liegenden Business Entitys, z. B.:

public static final int DESCRIPTION = 2;

Über diese Zahlenwerte werden GUI-Felder in der BookMaintenance als Empfänger von Meldungen der jeweiligen Konstante registriert.

Anschließend werden neben den typischen Validationsmethoden wie isBusinessKeySpecified oder isDefined jene Methoden implementiert, die spezifisch für das Business Entity „Book“ sind (isDescriptionSpecified, isEmployeeValid, isFormatSpecified).

protected boolean isEmployeeValid(Book book) {

PartnerValidation val = PartnerValidation.getInstance();

boolean valid = val.isDefined(book.getEmployee());

if (book.isBorrowed()) {

valid &= val.isSpecified(book.getEmployee());

valid &= val.isUsable(book.getEmployee());

}

return valid;

}

Diese Prüfmethoden werden von der Methode validate aufgerufen und eventuelle Fehlermeldungen durch Setzen der „ProgramMessageId“ an den richtigen Empfänger umgelenkt.

protected void validate(CisObject obj) {

Book book = (Book) obj;

mm.setProgramMessageId(DESCRIPTION);

isDescriptionSpecified(book);

mm.setProgramMessageId(FORMAT);

isFormatSpecified(book);

mm.setProgramMessageId(EMPLOYEE);

isEmployeeValid(book);

}

 

10.21 Behandlung NLS-Attribute

Die Klasse NLSData[8] dient der Verwaltung von mehrsprachigen Attributen eines Business Objects. Für jedes NLS-Attribut muss eine NLSData-Instanz erfasst werden. Als Parameter wird der Attributpfad des NLS-Attributes übergeben. Als Beispiel zeigt die folgende Anweisung die Anlage eines NLS-Objektes für das Attribut description des Business Objects „Book“:

NlsDesc = new

NLSData(“com.cisag.app.edu.obj.Book:description”);

Die NLSData-Instanz muss an das zugehörige GUI-Eingabefeld gebunden werden, damit die anderen Sprachen vom Feld geführt werden. Das geschieht mithilfe

description.setNLSData(nlsDesc);

Lesen eines NLS-Attributes

Eine Business-Object-Instanz wird in einer bestimmten Inhaltssprache gelesen. Das ist entweder die für den Benutzer eingestellte Inhaltssprache (Standard ist die Hauptsprache der Datenbank) oder sie wird beim Lesen der Instanz explizit angegeben. Zum Lesen eines NLS-Attributes zu einer Business-Object-Instanz dient die load()-Methode der Klasse NLSData. Dieser muss die nicht transiente Business-Object-Instanz übergeben werden. Die Methode öffnet die Attributwerte aller auf der Datenbank vorhandenen Sprachen, außer der Inhaltssprache der Business-Object-Instanz, in die NLS-Instanz. Der Attributwert zu der Hauptsprache ist in der Business-Object-Instanz selbst gespeichert.

Der folgende Quelltextauszug zeigt die Prozedur für das Lesen eines NLS-Attributes in der SingleObjectEntity:

public NLSData getNLSData(String attribute) {

String fullName = type.getName() + ‘:’ + attribute;

NLSData nlsData = (NLSData) nls.get(fullName);

if (nlsData == null) {

nlsData = new NLSData(fullName);

if (isPersistent()) nlsData.load(om.getObject(model.get_primaryKey()));

nls.put(fullName, nlsData);

}

return nlsData;

}

Diese Methode wird beispielsweise in der BookMaintenance für das mehrsprachige Feld „description“ aufgerufen.

description.setNLSData(entity.getNLSData(“description”));

Speichern einer NLS-Instanz

Zum Speichern einer NLS-Instanz dient die Methode „update()“ der Klasse NLSData. Dieser muss die nicht transiente Business-Object-Instanz übergeben werden. Es werden die Attributwerte zu allen Datenbanksprachen gespeichert, außer der Inhaltssprache der Business-Object-Instanz. Der Attributwert der Inhaltssprache wird beim „putObject()“ der Business-Object-Instanz gespeichert. Es ist zu beachten, dass die Business-Object-Instanz immer zuerst zum Speichern registriert wird. Erst dann dürfen die NLS-Attribute zum Speichern registriert werden.

Der folgende Quelltextauszug zeigt die Prozedur für das Speichern von NLS-Attributen in der Methode „update“ der SingleObjectValidation:

CisObject pers = om.getObject(key, CisObjectManager.READ_WRITE);

om.putObject(pers);

for (Iterator i = nls.values().iterator(); i.hasNext();

(NLSData)i.next()).update(pers));

Zum Feststellen, ob ein Benutzer Änderungen vorgenommen hat, dient die Methode isChanged() der Klasse NLSData.

10.22 Selection Group

Die „Selection Group“ einer Anwendung dient zur Führung einer Liste von Parametern, die jeweils einen Namen, Typ und Wert besitzen. So eine Parameterliste wird normalerweise einer Anwendung als Eingabeparameter übergeben (Methode run()) oder sind die Ausgabeparameter der Anwendung. Die Selection Group stellt Funktionalitäten bereit, um Werte direkt aus GUI-Feldern zu übernehmen bzw. zu setzen. Dazu muss das GUI-Feld mit dem Parameternamen an der Selection Group registriert werden, für den es den Wert liefern bzw. übernehmen soll.

Ihre Selection Group kann die Anwendung mit der Methode getSelectionGroup() abfragen. Die Selection Group benutzt die Methoden getValueAsObject() bzw. setValueFromObject() sowie getStringValue() bzw. setStringValue() eines registrierten Feldes, um den Wert eines Feldes zu speichern oder zu setzen. Damit bestimmt das Feld, welche Information in der Selection Group gespeichert und wie die anzuzeigende Information wieder hergestellt wird. Zum Beispiel wird bei Textfeldern der enthaltene String geliefert bzw. ins Feld gesetzt. Business-Entity-Felder werden besonders behandelt. Bei einem Business-Entity-Feld wird das im Feld gespeicherte Business-Entity über die Methode getEntityObject() von der Selection Group abgefragt. Von der erhaltenen Instanz wird der Primärschlüssel mit get_primaryKey() und vom Business-Entity-Feld die GUID der Datenbank über die Methode getDatabaseGuid() abgefragt. Dadurch wird eine Business-Object-Instanz eindeutig identifiziert. Diese Werte werden von der Selection Group gespeichert und später wieder zum Lesen der Business-Object-Instanz benutzt. Die Methode setEntityObject() des Business-Entity-Feldes wird von der Selection Group benutzt, um die gelesene Instanz dem Feld zuzuweisen.

Die Registrierung eines Feldes an der Selection Group erfolgt über eine add()-Methode, welcher der Parametername und das Feld übergeben wird. Optional können noch Flags angegeben werden. Der Typ des Parameters wird über den dem Feld zugeordneten logischen Datentyp bestimmt. Die Signaturen der Methoden sind:

public void add(String parameterName, Field field);

public void add( String parameterName, Field field, int flags )

Mögliche Flags sind KEY und KEY_WITH_LOAD. Das Flag KEY kennzeichnet einen Parameter als Schlüssel. Zurzeit hat diese Kennzeichnung noch keine weitere Auswirkung, sie könnte aber zukünftig dafür genutzt werden, die zugehörigen Felder auf der Oberfläche zu Markieren. Das Flag KEY_WITH_LOAD kennzeichnet einen Parameter ebenfalls als Schlüssel, aber führt außerdem dazu, dass nach erfolgter Suche auf dem Feld das gewählte Business Object geöffnet wird.

Die Selection Group speichert die registrierten Parameter in einer Parameterliste (CisParameterList), die mit der Methode getParameters() abgefragt werden kann. Ebenso kann eine ganze Parameterliste mit der Methode setParameters() der Selection Group hinzugefügt werden. Dabei werden Werte für registrierte Felder in diese übernommen, so dass auf diesem Wege die Inhalte der registrierten Felder aktualisiert werden können.

Über die Methode clearParameters() werden die an der Selection Group registrierten Felder geleert. Über die Methode remove() kann ein einzelner Parameter aus der Selection Group entfernt werden, über die Methode removeAll() alle Parameter.

10.23 Anwendungsaktionen

In den Metadaten zu einer Anwendung müssen unter dem Karteireiter „Aktion“ die Anwendungsaktionen eingetragen werden. Eine Aktion hat einen aussagekräftigen Namen, ihr sind ein Typ, eine Action-ID und ein oder mehrere Parameter zugeordnet. Der Typ bestimmt, ob es sich um Übergabeparameter oder Rückgabeparameter der Anwendung handelt. Die Action-ID dient der Identifikation in der run()-Methode der Anwendung, welche die Parameter als Parameterliste übergeben bekommt. Durch diese Einträge kann das System für die Kontextmenüs (z. B. bei Business-Entity-Feldern) feststellen, welche Anwendung das Business Entity verarbeiten kann. Der Beispielanwendung BookMaintenance kann beim Start eine Instanz des Business Objects „Book“ übergeben werden. Das geschieht zum Beispiel beim Aufruf eines Buchs aus der Historie. Die folgende Abbildung zeigt die Metadaten:

10.24 Anwendungswertehilfe (Suche)

Eine Anwendung kann den Navigationsbereich für eine Anwendungswertehilfe benutzen: eine Suche unter dem Karteireiter „Suche“ im Navigationsbereich. Dazu gibt die Anwendung über die Methode setSearchName() eine im Repository definierte Suche an. Diese Initialisierung erfolgt in der init()-Methode. Die folgende Anweisung zeigt das Setzen einer Suche in der SingleObjectMaintenance:

protected void initLocator() {

setSearchName(dictionary.getType().getName());

}

Diese Methode sollte in den einzelnen Implementierungen überschrieben werden. Bei setSearchName sollte der konkrete Name der Suche angegeben werden.

Wählt der Benutzer ein Suchergebnis aus, so wird es in die Anwendung übernommen. Dazu wird die run()-Methode der Anwendung aufgerufen. Als Parameter wird die Action-ID INIT_LOAD und eine Parameterliste vom Typ CisParameterList übergeben. Diese enthält beim Ergebnis einer Suche einen speziellen Parameter mit den Namen des Basis-Business-Objects der Suche (wenn angegeben). Diesem Parameter sind als Wert der Primärschlüssel und die Datenbank-GUID des Business Objects zugeordnet. Damit lässt sich eine Business-Object-Instanz eindeutig identifizieren. Als weitere Parameter enthält die Paramterliste die bei der Suchdefinition angegebenen Rückgabeparameter. Die run()-Methode muss die Parameter der Parameterliste auswerten. Beim Hinzufügen der Parameterliste zur „Selection Group“ der Anwendung wird die Business-Object-Instanz über den Persistenzdienst geladen und diesem Parameter zugewiesen. Die registrierten GUI-Felder bekommen die Werte der Parameter zugewiesen. Deshalb ist es praktisch das Entity-Feld zu diesem Business Object unter dessen Namen an der Selection Group zu registrieren.

Wird eine Anwendungswertehilfe verwendet, sollte in den Metadaten der Anwendung unter dem Karteireiter „Aktion“ die zugehörige „Aktion“ vom Typ INIT_LOAD (ID 1) eingetragen werden. Als Namen der Aktion sollte „Load“ verwendet werden. Unter dem Karteireiter „Aktion“ wird festgelegt, was die Anwendung an Parametern unter welcher Action-ID entgegen nimmt. Bei einer Anwendungswertehilfe wird standardmäßig zu den Rückgabeparametern der Suche der technische Schlüssel der ausgewählten Business-Object-Instanz hinzugefügt. Der zugehörige Parametername entspricht dem des Basis-Business-Objects. Dafür und für die übrigen Parameter der Suche werden die entsprechenden Parameter zu der Action angegeben. Dazu wird deren Name und Typ eingetragen. Wurde ein Business Object als Parameter angegeben, so wird das Kontextmenü zu dem Business Object um diese Anwendung erweitert, so dass die Anwendung direkt mit diesem Business Object aufgerufen werden kann.

10.25 Feldwertehilfe

Um ein Eingabefeld mit einer Wertehilfe auszustatten, muss in den Metadaten der zugehörigen Data-Description eine Suche als Wertehilfe eingetragen werden. In der Suchdefinition muss bei den Rückgabeparametern die Anzeigeart angegeben werden. Diese bestimmt, welcher Wert in das Feld als Suchergebnis übernommen wird. Es kann eine der folgenden Anzeigearten bei einem Rückgabeparameter eingestellt werden:

  • Bei der Anzeigeart „Identifikation“ wird beim Start der Suche aus einem Eingabefeld der Inhalt des Feldes in diesen Suchparameter übernommen. Enthält das Feld eine Wildcard, wird die Suche sofort ausgeführt. Bei der Rückgabe des vom Benutzer ausgewählten Suchergebnisses wird der Wert dieses Parameters in das auslösende Feld zurückgestellt. Handelt es sich um ein ExtendedTextField wird der Wert in den vorderen Teil übernommen. Es darf höchstens einen Parameter mit der Anzeigeart „Identifikation“ in der Suche geben.
  • Die Anzeigeart „Bezeichnung“ wird nur für eine Wertehilfe bei einem Eingabefeld vom Typ ExtendedTextField benutzt. Wird die Suchergebnismenge als ComboBox visualisiert, enthält die ComboBox die Werte von „Identifikation“ und „Bezeichnung“ hintereinander. Bei der Rückgabe des vom Benutzer ausgewählten Suchergebnisses in ein ExtendedTextField wird der Wert dieses Parameters in den hinteren Teil des Feldes geschrieben. Es darf höchstens einen Parameter mit der Anzeigeart „Text“ in der Suche geben.
  • Die Anzeigeart „Kein“ wird für sonstige Rückgabeparameter verwendet, die keinen Einfluss auf die Benutzung der Suche als Wertehilfe haben.

10.26 Entity-Felder

Ein Entity-Feld ist ein spezielles GUI-Textfeld für ein Business Entity, in dem dessen Business Key angezeigt wird. Ein konkretes Entity-Feld, z. B. das BookField, ist abgeleitet von der abstrakten Klasse EntityField, welche von der Klasse ExtendedTexField erbt. Es besitzt erweiterte Funktionalitäten der GUI. So bietet ein Entity-Feld ein Kontextmenü, in dem weitere Aktionen auf dem Entity angeboten werden. Es unterstützt Drag & Drop von Business Entitys. Entity-Felder sind GUI-Elemente und liegen im Unternamensraum .gui.

Ein Entity-Feld hat folgende wesentliche Methoden:

  • Die Methode getEntityObject() liefert zu dem vom Benutzer eingegebenen Wert (der Business Key) das Business Entity.
  • Die Methode setEntityObject() speichert ein Business Entity im Feld und zeigt dessen Business Key sowie dessen Bezeichnung an.
  • Die Methode getDatabaseGuid() liefert die GUID der Datenbank, auf der das akutell im Feld angezeigte Business Entity gespeichert ist.

Entity-Felder können mit Business Entitys von beliebigen Datenbanken arbeiten, bei Drag & Drop wird die Datenbank der eingefügten (Drag & Drop) Business-Entity-Instanz übernommen. Um Instanzen nur von einer bestimmten Datenbank zuzulassen, kann die erlaubte Datenbank vorgegeben werden. Dabei ist standardmäßig die OLTP-Datenbank als die einzig zugelassene Datenbank zu setzen. Mit der Methode setFixedDatabaseGuid() kann die zulässige Datenbank am Feld gesetzt und mit getFixedDatabaseGuid() abgefragt werden. Der Rückgabe- bzw. der Übergabeparameter null bedeutet, dass alle Datenbanken akzeptiert werden.

Klasse SimpleEntityField

Für ein einfaches Business Entity kann eine eigene Entity-Feld-Klasse recht einfach durch Ableitung von der Klasse com.cisag.pgm.gui.SimpleEntityField erzeugt werden.

Ein einfaches Business Entity hat folgende Merkmale:

  • der Primärschlüssel ist nur eine GUID,
  • der Business-Key ist nur ein String-Attribut,
  • es hat nur ein Beschreibungsattribut, welches zur Bildung des Instanzstrings und zum Füllen des Description-Bereichs im ExtendedTextField benötigt wird.

Die Klasse SimpleEntityField besitzt zusätzlich die folgenden Methoden zur Steigerung des Komforts bei der Benutzung:

  • Die Methode getGuid() liefert den Primärschlüssel des im Feld angezeigten Business Entitys.
  • Die Methode setGuid() lädt zu einem Primärschlüssel das Business Entity und zeigt dessen Business Key an.

Im folgenden Beispiel wird die Implementierung des Entity-Feldes BookField gezeigt. Bei der Erzeugung des UI-Feldes wird die Methode initializeBusinessEntity() aufgerufen. Diese muss überschrieben werden. Es ist nicht notwendig, die Methode super.initialize() aufzurufen, wenn die Klasse direkt von SimpleEntityField erbt.

Standardmäßig wird davon ausgegangen, dass das Business Entity auf der OLTP-Datenbank existiert. Das Setzen der Datenbank erfolgt über die Methode setFixedDatabaseGuid(). Die Methode setBusinessEntity() bindet das Business Entity ans Feld. Dabei wird die Entity-Klasse, der Methodenname für die Generierung des technischen Schlüssels aus dem Business Key, der Methodenname zur Abfrage des Business Keys und der Methodenname zur Abfrage der Beschreibung übergeben.

 

package com.cisag.app.edu.gui;

 

import com.cisag.app.edu.obj.Book;

import com.cisag.pgm.appserver.CisTransactionManager;

import com.cisag.pgm.gui.SimpleEntityField;

 

public class BookField extends SimpleEntityField {

public BookField(String guid, String path) {

this(guid, path, true);

}

public BookField(String guid, String path, boolean description) {

super(guid, path, description);

}

protected void initializeBusinessEntity() {

setFixedDatabaseGuid(CisTransactionManager.OLTP);

setBusinessEntity(Book.class, “buildByCodeKey”, “getCode”, “getDescription”);

}

}

In der Anwendung wird das Entity-Feld wie jedes andere Feld instanziiert. Das Flag „description“ gibt an, ob im hinteren Feldteil der Wert des Beschreibungsattributs, hier „description“, gezeigt werden soll.

number = new BookField(Guid.AUTOGUID, “.number”, false);

Bei einem Business Object mit komplexerem Business Key muss der Entwickler selbst die Methoden getEntityObject(), setEntityObject() implementieren, da er sich dann eventuell um die Daten aus mehreren GUI-Feldern kümmern muss. Für die bei Drag & Drop angezeigte Beschreibung wird die Methode get_instanceString() des Business Objects benutzt. Diese muss gegebenenfalls überschrieben werden.

Kontextmenü

Ein Entity-Feld hat ein Kontextmenü, in dem die Anwendungen und Funktionen aufgeführt sind, die mit dem Business Entity arbeiten können, d. h. die eine Aktion INIT_LOAD mit einem Parameter vom Typ des entsprechenden Business Entitys besitzen. Damit das System die anzuzeigenden Anwendungen und die Standardanwendung bestimmen kann, muss das entsprechende Business Entity als primäres Entity bei der Anwendungsdefinition angegeben und als Eingabeparameter der Anwendung festgelegt sein.

Die folgende Abbildung zeigt als Beispiel die Anwendung BookMaintenance, die das Business Object „Book“ als primäres Entity zugeordnet hat:

Die folgende Abbildung zeigt als Beispiel die Anwendung „BookMaintenance“, die das Business Object „Book“ als möglichen Startparameter zugeordnet hat:

Ausschnitt aus der Anwendung „Entwicklungsobjekte“, Typ „Anwendung“, Karteireiter „Aktion“

10.27 Standard-Symbolleiste

Die Standard-Symbolleiste nimmt die Haupt-Buttons zu einer Anwendung auf. Beim Erzeugen der Anwendung kann das Aussehen der Standard-Symbolleiste beeinflusst werden. Die entsprechenden Schalter müssen mit den Schaltern AUTOMATIC_ENTER und AUTOMATIC_EXIT mit „binärem Oder“ (|) verknüpft werden.

Ein Beispiel:
super(AUTOMATIC_EXIT |
AUTOMATIC_ENTER |
AUTOMATIC_DUPLICATE_DISABLED |
AUTOMATIC_MARK_FOR_DELETION);

Diese Schalter stehen zur Verfügung:

  • AUTOMATIC_CREATE_DIALOG
    Die Anwendung öffnet ein Dialog-Fenster, wenn der „Neu“-Button gedrückt wird. Durch diesen Schalter wird „Neu“ als „Neu…“ dargestellt.
  • AUTOMATIC_DUPLICATE_DIALOG
    Die Anwendung öffnet ein Dialog-Fenster, wenn der „Duplizieren“-Button gedrückt wird. Durch diesen Schalter wird „Duplizieren“ als „Duplizieren…“ dargestellt.
  • AUTOMATIC_DUPLICATE_DISABLED
    Die Anwendung unterstützt kein Duplizieren. Der „Duplizieren“ Button wird ausgeblendet. Dies sollte erfolgen, wenn die Anwendung in keinem Fall ein Duplizieren zur Verfügung stellt. Dies gilt beispielsweise für Abfrage-Anwendungen.
  • AUTOMATIC_DELETE_DIALOG
    Die Anwendung öffnet ein Dialog-Fenster, wenn der „Löschen“-Button gedrückt wird. Mit diesem Schalter wird der „Löschen“-Button als „Löschen…“ dargestellt.
  • AUTOMATIC_MARK_FOR_DELETION
    Die Anwendung unterstützt das Setzen und Entfernen von Löschkennzeichen. Mit diesem Schalter wird anstelle des „Löschen“-Buttons ein Menü erstellt, das die Einträge „Löschen“, „Löschkennzeichen setzen“ und „Löschkennzeichen entfernen“ enthält.
  • AUTOMATIC_MARK_FOR_DELETION_DIALOG
    Beim Setzten oder Entfernen des Löschkennzeichens öffnet die Anwendung ein Dialog-Fenster. Durch Angabe dieses Schalters werden die Menüeinträge „Löschkennzeichen setzen“ und „Löschkennzeichen entfernen“ durch „Löschkennzeichen setzen…“ und „Löschkennzeichen entfernen…“ ersetzt.

Aufgrund der einem Benutzer systemweit zugewiesenen Berechtigungen darf er eventuell einige der Standardaktionen wie z. B. „Löschen“ nicht ausführen. In diesem Fall muss der zugehörige Button auf „disabled“ gesetzt werden. Damit die Standard-Symbolleiste entscheiden kann, für welche Buttons das der Fall ist, bekommt sie die Klasse des Business Objects mitgeteilt, das von der Anwendung verwaltet wird. Dazu wird die Methode setEntity() der Standard-Symbolleiste aufgerufen und die Business-Object-Klasse übergeben. Diese Initialisierung der Standard-Symbolleiste sollte in der init()-Methode erfolgen.

Ein Beispiel:

MainCoolBar mcb = getMainCoolBar();

mcb.setEntity(dictionary.getDatabaseAlias(),

dictionary.getType());

Die Variable „dictionary“ wurde im Konstruktor der Klasse „SingleObjectMaintenance“ definiert.

Die Anwendung kann auch explizit Buttons in der Standard-Symbolleiste auf „enabled“ oder „disabled“ setzen. Dazu kann die zum Button korrespondierende Action an der Standard-Symbolleiste abgefragt werden. Das folgende Beispiel setzt den Delete-Button auf „disabled“, wenn die Business-Object-Instanz im Modell nicht persistent ist. In diesem Fall handelt es sich um eine Neuanlage, die noch nicht auf der Datenbank gespeichert ist und somit nicht gelöscht werden kann.

MainCoolBar cb = getMainCoolBar();

cb.getDeleteAction().setEnabled(data.is_persistent());

Mit dem Aktivieren der Aktion über setEnabled(true) kann nicht die Einstellung aus der globalen Berechtigung außer Kraft gesetzt werden.

10.28 Verlauf

Der Verlauf ist ein Komfortmerkmal des ERP-Systems. Mit dem Verlauf kann sich ein Benutzer einen Überblick über bearbeitete Daten und aufgerufene Anwendungen verschaffen. Im Verlauf werden die aufgerufenen Anwendungen inklusive der benutzten Daten, meistens sind das die Business Entitys, in ihrer Aufrufreihenfolge gespeichert. Der anwendungsbezogene Verlauf merkt sich die zuletzt in einer Anwendung benutzten Daten. Die Sortierung der Einträge übernimmt das System. Beim Start einer Anwendung werden automatisch die zuletzt benutzten Daten geöffnet, indem der Anwendung die entsprechenden Parameter übergeben werden.

Die Aufzeichnung des Verlaufs muss durch die Anwendung unterstützt werden. Dazu wird die „Selection Group“ der Anwendung benutzt. Die Anwendung registriert die GUI-Felder bei der „Selection Group“, deren Inhalte gespeichert werden sollen. Das sind normalerweise die Felder, die zum Öffnen der anzuzeigenden Daten nötig sind. Bei einem Business Object sind dies beispielsweise der Primärschlüssel und die Datenbank-GUID. In der Anwendung „Book-Maintenance“ wird das Entity-Feld für das Business Entity „Book“ unter dem Parameternamen „code“ der „Selection Group“ hinzugefügt. Das in diesem Fall verwendete Flag wird für die hier beschriebene Funktion nicht benötigt. Da es sich aber um ein Schlüsselfeld handelt, muss dieses entsprechend gekennzeichnet werden. Das Flag „KEY_WITH_LOAD“ gibt an, dass bei diesem Feld nach Auswahl eines Wertes aus der Wertehilfe automatisch das gewählte Business Object in der Anwendung geöffnet wird.

getSelectionGroup().add(“code”, code,

SelectionGroup.KEY_WITH_LOAD);

Wurde ein Business Entity geöffnet oder erfasst, dann soll es im Verlauf unter einer aussagekräftigen Bezeichnung erscheinen. Es müssen die notwendigen Daten hinterlegt werden, um die Business-Object-Instanz wieder öffnen zu können. Dazu dient die Methode recordAction() der Anwendung. Diese speichert die notwendigen Daten zu der Business-Object-Instanz, damit diese wieder durch die run()-Methode der Anwendung gelesen werden kann. Die Daten bestehen aus der Action-ID „INIT_LOAD“ und der Parameterliste der „Selection Group“, welche die Werte der registrierten Felder enthält. Richtigerweise kann die Anwendung so beliebige Daten speichern und nicht nur die Daten zu einem Business Entity. Der dritte Parameter nimmt eine aussagekräftige Bezeichnung auf, die vom System im Verlauf angezeigt wird und die gespeicherten Daten repräsentiert.

Die folgende Anweisung aus der „SingleObjectMaintenance“ zeigt den Aufruf von recordAction():

recordAction(CisUiApplication.INIT_LOAD,

getSelectionGroup().getParameters(),

entity.getMainObject().get_instanceString());

Wählt der Benutzer einen Eintrag aus dem Verlauf aus, dann wird die run()-Methode der zugehörigen Anwendung aufgerufen. Als Parameter wird die gespeicherte Action-ID und die Parameterliste übergeben. Die Anwendung muss wissen, wie sie die Daten der Parameterliste auszuwerten hat. Normalerweise wird die Parameterliste der „Selection Group“ mit setParameters() hinzugefügt und damit die Daten in die registrierten Felder übertragen. Anschließend wird die load()-Methode der Anwendung ausgeführt.

Wird z. B. ein Business Entity in einer Anwendung gelöscht, dann muss die Anwendung dafür Sorge tragen, dass auch dessen Eintrag im Verlauf entfernt wird. Dazu ruft die Anwendung nach erfolgreicher Löschung die Methode removeRecordedAction() auf:

removeRecordedAction(CisUiApplication.INIT_LOAD, getSelectionGroup().getParameters(), entity.getMainObject().get_instanceString());

Um den zu löschenden Eintrag zu identifizieren, müssen der Methode als Parameter die Action-ID, die Parameterliste und die Beschreibung übergeben werden. Der nachfolgende Sourcecode-Auszug zeigt das Löschen des Eintrages im Verlauf zu einem gelöschten Business Entity. Die Parameterliste und die Beschreibung müssen vor dem Löschen abgefragt werden.

case Action.CNF_DELETE:

dataFromUi();

CisParameterList params = getSelectionGroup().getParameters();

String instanceString = entity.getMainObject().get_instanceString();

if (delete()) {

removeRecordedAction(params, instanceString);

}

dataToUi();

break;

Die von einer Anwendung verstandenen Parametersets und die zugeordnete Action-ID müssen als Metadaten zur Anwendung erfasst werden. Das erfolgt unter dem Karteireiter „Aktion“.

10.29 Eigene Data-Descriptions pro Anwendung

In den bisherigen Beispielanwendungen wurde immer die zum logischen Datentyp des Business-Object-Attributs angegebene Data-Description für die Darstellung der GUI-Felder verwendet. Soll bei einer der Anwendungen eine abweichende GUI-Darstellung der Felder (z. B. Tooltip, Label, Direkthilfe usw.) benutzt werden, würde eine Änderung der Data-Description auch die Darstellung bei den anderen Anwendungen ändern. Das ist nicht erwünscht. Um diesem ungewollten Seiteneffekt vorzubeugen, sollte möglichst jede Anwendung für die Darstellung eines GUI-Feldes ihre eigene Data-Description anlegen. Da eine Data-Description immer an einen logischen Datentyp gebunden ist, muss dazu ein vom logischen Datentyp des Business-Object-Attributs abgeleiteter logischer Datentyp angelegt werden. Die logischen Datentypen und Data-Description werden im ui-Namensraum der Anwendung angelegt, um ihre Zugehörigkeit zu einer Anwendung zu kennzeichnen.

Beispiel:

LDT com.cisag.app.edu.BookDescription, erbt von com.cisag.pgm.datatype.Description, String(65, ml).

LDT com.cisag.app.edu.ui.BookMaintenanceDescription, erbt von com.cisag.app.edu.BookDescription

10.30 Implementierung der Klasse BookMaintenance

In der Klasse BookMaintenance werden jene Methoden der Superklasse SingleObjectMaintenance überschrieben, welche die Book-spezifische Initialisierung des Arbeitsbereichs (initWorkArea) und den Datenaustausch zwischen GUI-Feldern und Datenmodell (dataToUi, dataFromUi) vornehmen.

In der initWorkArea werden für alle Book-Attribute GUI-Felder erzeugt, auf der Pane platziert und als Empfänger von Meldungen mit einer bestimmten Nummer registriert. Im folgenden Quellcodeausschnitt sind diese drei Initialisierungsschritte für das Feld „description“ angeführt:

description = new TextField(“0240985C22041F10B0E4846496200000”, “.description”);

pane.add(description, “1”);

description.registerMessage(BookValidation.DESCRIPTION);

Zusätzlich werden noch spezielle Initialisierungsschritte für bestimmte Felder gesetzt. So wird etwa das Feld „code“ zur Selection Group hinzugefügt und für das Feld „description“ der NLS Support gesetzt.

getSelectionGroup().add(“code”, code, SelectionGroup.KEY_WITH_LOAD);

description.setNLSData(entity.getNLSData(“description”));

10.31 Überschreiben von get_instanceString()

Zum Überschreiben wird eine vom Business Object abgeleitete Klasse eingeführt, welche die neue Implementierung von get_instanceString() enthält. Die neu generierten Sourcen verwenden dann intern die abgeleitete Klasse. Für die Anwendung ändert sich nichts, sie benutzt die allgemeinere Klasse. Die Vererbungsbeziehung muss in den Metadaten des Business Objects erfasst werden. Unter dem Karteireiter „Weitere Eigenschaften“ beim Punkt „Sonstige Einstellungen“ wird der Name der neuen Klasse eingetragen (siehe Abbildung). Die Klasse muss noch als Entwicklungsobjekt „Java-Klasse“ erfasst werden und das Business Object muss neu generiert werden.

Sie dürfen nur die Methoden get_instanceString() und get_permission(…) einer Business–Object-Klasse überschreiben. In der Anwendung darf nicht direkt auf die neue Klasse (z. B. BookImpl) ein Class-Cast erfolgen. Dazu wird immer die eigentliche Business-Object-Klasse (z. B. Book) benutzt. Dadurch wird die Anwendung stabiler gegen Änderungen an diesem Vererbungsmechanismus.

Verwendung des Persistenzdienstes

Die Verwendung des Persistenzdienstes innerhalb von get_instanceString() ist nur über die Methode resolveForeignKey() der Klasse CisObjectUtility[9] erlaubt. Damit kann zu einem Fremdschlüssel eine Business-Object-Instanz geladen werden. Die Methode liefert eine transiente Kopie der Business-Object-Instanz in der Datenbanksprache des aufrufenden Business Objects zum übergebenen technischen Schlüssel und der Datenbank, auf der das Business Object liegt. Die Methode ist fehlertolerant bei nicht existenten Objekten (z. B. zu ladenes Objekt existiert nicht auf der angegebenen Datenbank) und benutzt eine eigene Transaktion (baut nicht auf die Dummy-Transaktion). Die Signatur ist:

public static CisObject resolveForeignKey(CisObject source,

String targetDatabaseAlias, byte[] targetPrimaryKey);

Exemplarisch wird nachfolgend die Implementierung einer von der Klasse XYZ abgeleiteten Klasse XYZImpl gezeigt, in welcher der beschreibende Instanzstring aus dem Business Key eines referenzierten Business Objects und dem eigenen Business Key gebildet wird. Ein Beispiel: Bei einem Auftrag ist der Business Key aus einem Typ (GUID des Typs (Fremdschlüssel)) und der Auftragsnummer zusammengesetzt. Der anzuzeigende Instanzstring soll aus der Identifikation des Auftragtyps und der Auftragsnummer bestehen.

public class XYZImpl {

public String get_instanceString() {

//Vorschlagswert, wenn Datenbank oder XYZType nicht vorhanden.

String result = getCode();

byte[] typeGuid = getTypeGuid();

if (!Guid.isInvalidGuid(typeGuid)) {

//Nicht existierende  Objekte auslassen!

XYZType t = (XYZType) CisObjectUtility.resolveForeignKey(this,

CisTransactionManager.OLTP,

XYZType.buildPrimaryKey(typeGuid));

if (t != null) {

//zusammenbauen.

result = t.getCode() + ” ” + getCode(); // Art+Nummer

}

}

return result;

}

}

10.32 Arbeiten mit Tabellen

Eine Tabelle[10] dient der Visualisierung von vielen Datensätzen mit gleicher Struktur. Die Zeilen einer Tabelle haben immer den gleichen Aufbau.

10.32.1 Funktionsprinzip einer Tabelle

Eine Tabelle basiert auf einem Tabellenmodell. Für jede Zeile der Tabelle gibt es einen Datencontainer, der die Daten der Zeile aufnimmt. Der Datencontainer ist eine Instanz der Hilfsklasse CisListPartMutable, die ein Datenobjekt aufnehmen kann und von der Tabelle benötigte Funktionalitäten besitzt.

Der Zugriff auf das Datenobjekt erfolgt über die Methoden getData()/setData(). Für jede Spalte der Tabelle wird in der Anwendung ein Muster-GUI-Feld angelegt. Die Musterfelder dienen zum Datenaustausch zwischen Tabellen-Modell und den Tabellenzellen. Die Tabelle verwaltet ihre Datenstruktur zur Darstellung an der GUI selbst und kümmert sich für den Entwickler transparent um den Datenaustausch zwischen den Muster-GUI-Feldern und den Zellen.

Um den zeilenweisen Datenaustausch zwischen den Musterfeldern und dem Datencontainer des Modells kümmert sich die Tabelle ebenfalls selbstständig. Sie benutzt dazu die bereitgestellten Methoden dataFromTable() und dataToTable() des an ihr registrierten TableDataManagers. Eine Dialog-Anwendung, welche eine Tabelle verwendet, muss einen TableDataManager spezifizieren.

Die Methode dataFromTable() realisiert das Mapping von den Muster-GUI-Feldern in das Datenobjekt des Datencontainers der Modell-Tabellenzeile.

Die Methode dataToTable() implementiert das Mapping vom Datenobjekt des Datencontainers in die Muster-GUI-Felder.

Die nachstehende Abbildung stellt das Funktionsprinzip dar.

10.32.2 Gültigkeit des Tabellen-Modells

Die Tabelle verwaltet intern ein Gültigkeitsflag für ihr Modell. Jede Benutzung einer Tabellenmethode setzt den Modellstatus auf „valid“, nachdem die Inhalte der Tabellenzellen in das Modell übertragen wurden. Die Übertragung der Daten von der GUI zum Modell erfolgt nur bei einem invaliden Modell. Die Tabellen-Methode display() überträgt Daten vom Modell zur GUI. Sie muss immer explizit durch den Entwickler der Anwendung aufgerufen werden, sobald er seine Änderungen an dem Modell abgeschlossen hat. Beim Aufruf dieser Methode wird auch der Modellstatus der Tabelle wieder auf „invalid“ gesetzt, da danach normalerweise Änderungen durch den Benutzer passieren. Es ist wichtig, dass der Entwickler darauf achtet, die display()-Methode der Tabelle aufzurufen, bevor Benutzereingaben erwartet werden, um das Modell invalid zu setzen. Sonst werden gemachte Benutzereingaben nicht von der Tabelle ins Modell übernommen und die neu eingegebenen Daten in den GUI-Feldern werden mit dem nächsten display() mit den alten Werten aus dem Modell überschrieben.

Auch in der taskSwitch()-Methode erfolgt ein expliziter Aufruf von table.display(), um das Modell auf „invalid“ zu setzen, da vorher die Tabellenmethode isChangedRowAvailable() aufgerufen und dadurch das Modell auf „valid“ gesetzt wurde. Um weitere Eingaben des Benutzers zu übernehmen, muss der Modellstatus aber wieder auf „invalid“ stehen.

Die Daten von der GUI werden durch die Tabelle alle auf einmal in interne Hilfsfelder übertragen, so dass die GUI nicht zeilenweise abgefragt wird, was die Leistung erhöht. Danach wird die Abfrage der GUI sozusagen verriegelt. Der Aufruf der dataFromTable()/dataToTable()-Methoden (Mapping zwischen Modell und interne Tabellenhilfsfelder) erfolgt aber zeilenweise. Ein nochmaliges Verriegeln durch den Aufruf von Tabellenmethoden in diesen Routinen stört nicht, da die Daten bereits ausgetauscht wurden. Die Anwendung muss aber explizit die Tabelle wieder entriegeln (über table.display()), damit die Tabelle Eingaben des Benutzers übernimmt.

10.32.3 Initialisieren einer Tabellen-Instanz

Eine Instanz einer Tabelle wird über den Konstruktor der Klasse erzeugt, dem die notwendigen Initial-Parameter übergeben werden. Dies sind die GUID der Tabelle, die darzustellende Zeilenanzahl pro Seite, ein boolean-Flag, welches angibt, ob die Tabelle editierbar ist oder nicht, und der zugehörige TableDataManager. Basieren Tabellenspalten auf Attributen eines Business Objects kann über die setPath()-Methode der vollständige Name des Business Objects angegeben werden. Dann reicht es, bei der Erstellung der Musterfelder den relativen Attributpfad anzugeben. Über die Methode setSelectionMode() wird der für die Tabellenzeilen gewünschte Selektionsmodus eingestellt. Der folgende Quelltextauszug zeigt ein Beispiel für die Erstellung der Tabellen-Instanz table. Sie hat 39 Zeilen pro Seite[11], ist editierbar, die Dialog-Anwendung wird als TableDatamanager registriert. Es können mehrere Zeilenbereiche vom Benutzer ausgewählt werden.

Table table = new Table(Guid.AUTOGUID, 39, true, this);

table.setPath(“com.cisag.app.edu.obj.Book:”);

table.setSelectionMode(Table.MULTIPLE_INTERVAL_ SELECTION);

table.setVerticalScrollBarPolicy(ScrollPane.
VERTICAL_SCROLLBAR_NEVER);

10.32.4 Benötigte Methoden

Eine Dialog-Anwendung benötigt die folgenden Methoden für eine Tabelle:

  • Die Methode createTable() liefert die initialisierte Tabellen-Instanz.
  • Die Methode createColumns() konfiguriert die Spalten der Tabelle, indem den Spalten das jeweilige Muster-GUI-Feld zugewiesen wird.
  • Die Methode createRow() erstellt einen Datencontainer für eine Tabellenzeile im Modell.
  • Die Methode selectRow() positioniert den Fokus auf die angegebene Tabellenzeile.
  • Die Methoden dataToTable() und dataFromTable() dienen dem Datenaustausch. Die Implementierung ist stark anwendungsspezifisch und hängt von der Struktur des Datenobjektes des Datencontainers ab.

10.32.5 Meldungen bei Tabellen

Bei Tabellen ist das Setzen der korrespondierenden Ecken zu Meldungen an den Tabellenfeldern etwas aufwendiger. Die Message-IDs werden an den GUI-Musterfeldern der Tabelle registriert. Die Felder einer Spalte sind Kopien des der Spalte zugeordneten GUI-Musterfeldes. Damit ist diesen GUI-Feldern auch die entsprechende Message-ID zugeordnet. Um eine konkrete Zeile der Spalte zu identifizieren, ist ein zusätzliches Merkmal notwendig. Jedem Datencontainer (CisListPartMutable) einer Modell-Zeile wird eine eindeutige Zeichenkette zugeordnet. Diese Zeichenkette identifiziert die Zeile und wird vor dem Senden der Meldung am Message-Manager gesetzt. Zur Bildung einer eindeutigen Zeichenkette eignet sich zum Beispiel die GUID des angezeigten Business Objects, die mit der Methode toHexString() in einen String umgewandelt wird. Das Zuordnen der Zeichenkette zum Datencontainer erfolgt mit dessen setMessageGroup()-Methode. In der folgenden Quelltextzeile wird dem Datencontainer row der Hexadezimal-String der GUID des von ihm gespeicherten Business Objects obj zugeordnet:

row.setMessageGroup(Guid.toHexString(obj.getGuid()));

Soll eine Meldung für ein bestimmtes Feld in der Tabelle gesendet werden, muss dem Message-Manager mitgeteilt werden, bei welchem Feld die Meldungsecke erscheinen soll. Dazu wird dem Message-Manager mithilfe der Methode setProgramMessageGroup() die Zeichenkette übergeben, welche die Zeile identifiziert. Anschließend wird die entsprechende Message-ID am Message-Manager gesetzt. Nach dem Eintragen der Meldung in die Message-Queue mit sendMessage() kann die „Program Message Group“ des Message-Managers wieder geleert werden, damit weitere Meldungen keinen Bezug zur Zeile haben. Besser ist, wenn die Programmstellen, welche Meldungen senden, selbst explizit die „Program Message Group“ und -ID setzen. Der nachstehende Quelltextauszug zeigt die aufzurufenden Methoden:

mm.setProgramMessageGroup(Guid.toHexString(obj.getGuid())); mm.setProgramMessageId(CODE);

mm.sendMessage(…);

mm.setProgramMessageGroup(null);

mm.setProgramMessageId(0);

Die folgende Abbildung zeigt als Beispiel eine Message-Queue mit mehreren Meldungen, die über den Message-Group-String und die Message-ID den entsprechenden Tabellen-Feldern zugeordnet sind und an dieser Position die Meldungsecken anzeigen.

Bei der Benutzung von zwei Tabellen in einer Anwendung ist sinnvoll, an den Tabellen mit der Methode setMessageGroupPrefix(String) einen Zusatz-String für die Message-Group-Strings der Zeilen anzugeben. Damit werden eventuell gleiche Message-Group-Strings von Zeilen in den beiden Tabellen vermieden. Das kann passieren, wenn zum Beispiel dasselbe Business Object in den Tabellen angezeigt wird.

10.32.6 Sortieren in Tabellen

Damit die Tabellenzeilen in einer bestimmten Reihenfolge angezeigt werden, kann die Tabelle einen Komparator nutzen, der zur Sortierung der Zeilen verwendet wird. Als Komparator wird normalerweise die universelle Klasse SortOrderComparator genutzt, die eine Sortierreihenfolge in Form einer Instanz der Klasse „SortOrder“ übergeben bekommt. Die Sortierreihenfolge wird über die spezielle Action SortOrderAction generiert, der als Parameter der Name der Suche und die Anwendung als „Action Listener“ übergeben wird. Die Festlegung der Sortierattribute und die Default-Sortierreihenfolge erfolgt bei der Definition der Suche, welche die Daten für die Zeilen der Tabelle liefert. Die erzeugte Instanz der Klasse SortOrderAction wird zum Kopf der Tabelle mit der Methode addHeaderElement() hinzugefügt, um dem Benutzer ein Popup-Menü zu bieten, mit dem er sich seine gewünschte Sortierreihenfolge einstellen kann. Wurde vom Benutzer die Sortierreihenfolge geändert, benachrichtigt die SortOrderAction-Instanz die Dialog-Anwendung, damit diese die Tabelle neu sortieren kann. Es wird die performAction()-Methode aufgerufen, der eine Action mit der ID Action.SORTORDER_CHANGED und das SortOrder-Objekt übergeben wird. Das SortOrder-Objekt wird an die Komparator-Instanz übergeben. Die Tabelle sortiert ihre Zeilen mit der Methode sort() unter Nutzung der Komparator-Instanz um. Wird die Tabelle mit Zeilen gefüllt, muss die aktuell eingestellte Sortierreihenfolge berücksichtigt werden. Dazu wird der Suche das von dem Komparator gelieferte SortOrder-Objekt übergeben. Diese Sortierung wird dann statt der Default-Sortierung genutzt.

Ein Komparator vergleicht zwei Objekte gleichen Typs und implementiert im Wesentlichen die Methode compare(). Die Signatur der Methode ist:

int compare(Object o1, Object o2);

Das Ergebnis ist 0, wenn o1 gleich o2, -1, wenn o1 kleiner o2 und +1, wenn o1 größer o2 ist. Diese Methode wird durch die Tabelle aufgerufen, um die Zeilen zu sortieren. Um das Sortieren in Tabellen zu realisieren, werden folgende Klassen benötigt:

import com.cisag.pgm.objsearch.SortOrder;

import com.cisag.pgm.objsearch.SortOrderAction;

import com.cisag.pgm.objsearch.SortOrderComparator;

Die „SortOrderAction“ wird folgendermaßen erzeugt:

SortOrderAction a = new SortOrderAction(“com.cisag.app.edu.obj.Book”, this);

Bei der Initialisierung wird der Name der Suche und der „2Action Listener“ angegeben.

Der von der Tabelle zum Sortieren benötigte Komparator wird wie folgt instanziiert:

comparator = new SortOrderComparator(Book.class,

a.getSortOrder());

Der Komparator vergleicht die Objekte des angegebenen Klassentyps bezüglich der angegebenen Sortierordnung, die initial aus der Suche bzw. aus den Sortiereinstellungen des Benutzers ermittelt wurde. Die Sortierung basiert auf einer Ordnung von den in der Suche definierten Attributnamen für die Rückgabeparameter. Ist wie in diesem Beispiel als Klassentyp ein Business Object angegeben, müssen die angegebenen Namen der Attribute in der Suche den wirklichen Attributnamen des Business Object entsprechen, sonst kommt es zu einer Exception. Das ist deshalb notwendig, da der Zugriff auf die Attributwerte durch Reflection über die get()-Methoden für Attribute des Business Objects erfolgt und so zwingend die richtigen Namen benutzt werden müssen. Bei einem selbstdefinierten Objekt als Typ müssen der Suche entsprechende get()-Methoden für die Attribute angelegt werden.

Hat der Benutzer die Sortierreihenfolge geändert, muss die Anwendung als „Action Listener“ die Action behandeln. Dazu wird der Komparator neu konfiguriert, indem das mit der Action übermittelte SortOrder-Objekt am Komparator gesetzt wird. Anschließend sortiert sich die Tabelle unter Nutzung des Komparators neu:

case Action.SORTORDER_CHANGED:

comparator.setSortOrder((SortOrder) action.getState());

table.sort(comparator);

Beim Lesen der Zeilen aus der Datenbank muss man die aktuell eingestellte Sortierreihenfolge am Iterator setzen:

CisSearchIterator i = CisSearchIteratorFactory.getSearchIterator(

“com.cisag.app.edu.obj.Book”);

i.setSortOrder(comparator.getSortOrder());

Soll ein eigener Komparator implementiert werden, dann muss dieser für den Vergleich den Komparator des Transaktionsmanagers benutzen (Methode getComparator()) . Dieser gewährleistet, dass sich die Sortierung im Hauptspeicher äquivalent zu der Sortierung auf Datenbankebene verhält. Andernfalls wird der Benutzer mit verschiedenen Sortierreihenfolgen konfrontiert.

10.33 Abfragemuster merken

Bei Dialog-Anwendungen, die einen Abfragebereich mit Suchfeldern haben (z. B. Tabellenanwendungen), wird dem Benutzer die Möglichkeit geboten, sich eingegebene Abfragemerkmale zu speichern. Bei einem späteren Aufruf kann er diese dann wieder verwenden. Ein Abfragemuster wird mit dem zugehörigen Button in der Standard-Symbolleiste erstellt und in den anwendungsbezogenen Favoriten im Navigationsbereich abgelegt.

Die zu speichernden Feldinhalte müssen an der „Selection Group“ der Anwendung registriert werden. Das Registrieren eines Feldes erfolgt mit der Methode add() der „Selection Group“. Die folgende Anweisung registriert beispielsweise das Feld „codeSelection“ unter dem Parameternamen „Book“:

getSelectionGroup().add(“Book”, codeSelection);

Beim Öffnen eines Abfragemusters wird die run-Methode der Anwendung aufgerufen und das Abfragemuster als CisParameterList übergeben (Name/Wert-Paare). Die nachstehende Anweisung setzt die Werte wieder in die an der „Selection Group“ registrierten Felder:

getSelectionGroup().setParameters(params);

Anschließend kann zum Beispiel das Öffnen der Daten zur eingestellten Abfrage erfolgen.

10.34 Beispielanwendung: BookTableMaintenance

Diese Anwendung führt Bücher mithilfe einer Tabelle. Die Oberfläche der Anwendung ist geteilt in den Abfragebereich mit den Abfragefeldern und dem Arbeitsbereich mit der Tabelle. In der Tabelle werden die in der Datenbank gespeicherten Bücher entsprechend den Abfrageeinstellungen angezeigt. Sie sind nach Benutzerwunsch sortierbar.

In der Tabelle können mehrere Bücher auf einmal erfasst, geändert, gelöscht und gespeichert werden.

10.35 Arbeiten mit Listen

Eine Liste[12] dient der Visualisierung von vielen Datensätzen mit gleicher oder unterschiedlicher Struktur. Die Zeilen einer Liste müssen nicht denselben Aufbau besitzen, sie können unterschiedlich sein.

10.35.1 Funktionsprinzip einer Liste

Eine Liste basiert auf einem Listenmodell. Für jede Zeile der Liste existiert ein Datencontainer, der die Daten der Zeile aufnimmt. Der Datencontainer ist eine Instanz der Hilfsklasse CisListPartMutable, die ein Datenobjekt aufnehmen kann und von der Liste benötigte Funktionalitäten besitzt. Der Zugriff auf das Datenobjekt erfolgt über die Methoden getData()/setData(). Die Liste ordnet jeder Zeile im Modell eine Instanz der Klasse „ListView“ zu, die die GUI-Darstellung der Zeile übernimmt. Ein „ListView“ ist ein spezieller „VisualElementContainer“. Die Liste kümmert sich selbstständig um den zeilenweisen Datenaustausch zwischen der GUI-Struktur und den Datencontainern des Modells. Die Liste benutzt dazu die bereitgestellten Methoden des an ihr registrierten „ListViewCreators“ und des „ListViews“. Die ListView-Instanzen werden beim Blättern durch die Liste recycelt, es werden nur so viele erzeugt wie Zeilen auf einer Seite darstellbar sind.

Die Anwendung, die eine Liste benutzt, hat einen „ListViewCreator“ zu spezifizieren, welcher die Methode createView() fordert.

Diese liefert einen „VisualElementContainer“ der Klasse ListView, der einen Datencontainer (CisListPartMutable) aus dem Modell der Liste visualisieren kann. Die Klasse „ListView“ stellt die Methoden dataFromUi() und dataToUi() bereit, die der Liste zum zeilenweisen Datenaustausch zwischen dem Datencontainer im Modell und der GUI dienen. Der Datencontainer wird dabei als Parameter übergeben. Der Aufruf dieser Methoden erfolgt selbstständig durch die Liste. Die Klasse „ListView“ ist eine abstrakte Klasse, von der eine Klasse für den von der Liste zu benutzenden „ListView“ abgeleitet wird. Speziell die beiden Methoden dataFromUi() und dataToUi() des „ListViews“ müssen vom Anwendungsentwickler implementiert werden. Die Methode dataFromUi() realisiert das Mapping von den GUI-Feldern der ListView-Instanz in das Datenobjekt des Datencontainers der Listenzeile des Modells. Die Methode dataToUi() realisiert das Mapping vom Datenobjekt des Datencontainers in die GUI-Felder der ListView-Instanz. Die nachstehende Abbildung stellt das Funktionsprinzip dar.

10.35.2 Gültigkeit des Listen-Modells

Die Liste führt intern ein Gültigkeitsflag für ihr Modell. Jede Benutzung einer Listenmethode setzt den Modellstatus auf „valid“. Die Übertragung der Daten von der GUI zum Modell erfolgt nur bei einem invaliden Modell. Die Methode display() der Liste überträgt Daten vom Modell zur GUI. Sie wird explizit durch den Programmierer der Anwendung aufgerufen. Zusätzlich wird der Modellstatus der Liste auf „invalid“ gesetzt, da nach dem Anzeigen normalerweise Änderungen durch den Benutzer passieren und die Datenänderungen in der GUI-Struktur ins Modell übernommen werden sollen. Wichtig ist, dass der Entwickler darauf achtet, die display()-Methode der Liste aufzurufen, bevor Benutzereingaben erwartet werden, um das Modell invalid zu setzen. Sonst werden gemachte Benutzereingaben nicht von der Liste ins Modell übernommen und die neu eingegebenen Daten in den GUI-Feldern werden mit dem nächsten display() mit den alten Werten aus dem Modell überschrieben.

Auch in der taskSwitch()-Methode erfolgt ein expliziter Aufruf von list.display(), um das Modell auf „invalid“ zu setzen, da vorher die Listenmethode isChangedRowAvailable() aufgerufen und dadurch das Modell auf „valid“ gesetzt wurde. Um weitere Eingaben des Benutzers zu übernehmen, muss der Modellstatus aber wieder auf „invalid“ stehen. Es gilt also dasselbe Prinzip wie bei Tabellen.

Information: Intern basiert die Tabelle zum Teil auf einer Liste.

10.35.3 Initialisieren einer Listen-Instanz

Die Instanz einer Liste wird über den Konstruktor der Klasse erzeugt, dem die notwendigen Initial-Parameter übergeben werden. Dies sind die GUID der Liste, die darzustellende Zeilenanzahl pro Seite, ein boolean-Flag, welches angibt, ob die Liste editierbar ist oder nicht, und der zugehörige „ListViewCreator“. Basieren der Liste hinzugefügte GUI-Elemente auf Attributen eines Business Objects, dann kann über die setPath()-Methode der vollständige Name des Business Objects angegeben werden. Dann reicht aus, bei der Erstellung der GUI-Elemente den relativen Attributpfad anzugeben. Über die Methode setSelectionMode() wird der für die Listenzeilen gewünschte Auswahlmodus eingestellt. Mit der Methode setGuides() werden die Spalten- und deren linke und rechte Randbreiten konfiguriert. Der folgende Quelltextauszug zeigt ein Beispiel für die Erstellung der Listen-Instanz list. Sie hat 39 Zeilen pro Seite, ist editierbar, die Dialog-Anwendung wird als „ListViewCreator“ registriert. Mehrere Zeilenbereiche können vom Benutzer ausgewählt werden. Die Breite der Auswahlspalte ist 4 %, die der nächsten Spalte 30 % und die der letzten Spalte 60 % von der Gesamtbreite. Für die Auswahlspalte wir ein linker Rand von fünf Pixeln und ein rechter Rand von einem Pixel festgelegt. Die beiden anderen Spalten haben einen linken Rand von einem Pixel. Deren rechte Ränder haben den Defaultwert, der bei der Angabe von –1 genommen wird.

List list = new List(Guid.AUTOGUID, 39, true, this);

list.setPath(“com.cisag.app.edu.obj.Book:”);

list.setSeletionMode(List.MULTIPLE_INTERVAL_
SELECTION);

list.setGuides(null, new double[] {0.04, 0.3, 0.6},

new double[] {5, 1, -1},

new double[] {1, 1, -1});

Die Liste unterstützt (wie die Tabelle) die dynamische Anpassung der angezeigten Zeilen pro Seite an die Fenstergröße. Die einfachste Lösung ist, mit folgender Codezeile das (vertikale) Scrolling zu verbieten:

list.setVerticalScrollBarPolicy(ScrollPane. VERTICAL_SCROLLBAR_NEVER );

Die folgende Möglichkeit, sich stattdessen als „ActionListener“ bei der Liste zu registrieren und die Änderung der Zeilenzahl selbst durchzuführen, sollte nicht mehr verwendet werden:

list.setCurrentVisibleRowCountChangeAction(new Action(PAGE_SIZE_ADJUST_ACTION, this));

Beachten Sie, dass die automatische Anpassung nur funktioniert, wenn alle Zeilen dieselbe Höhe aufweisen.

Das Anzeigen der korrespondierenden Ecken zu Meldungen und das Sortieren von Listen funktioniert analog zur Tabelle.

10.35.4 Benötigte Methoden

Eine Dialog-Anwendung benötigt die folgenden Methoden zur Realisierung einer Liste:

  • Die Methode createList() liefert die initialisierte Listen-Instanz.
  • Die Methode createTitle() dient zur Initialisierung der Titelzeile der Liste (Spaltenüberschriften).
  • Die Methode createRow() erstellt einen Datencontainer der Klasse „CisListPartMutable“ für eine Listenzeile im Modell.
  • Die Methode createView() liefert eine ListView-Instanz, die von der Liste zur Visualisierung eines Modell-Datencontainers benutzt wird. Der Methode kann eine Zeichenkette übergeben werden, die als Bezeichnung für einen bestimmten ListView-Typ dient. Anhand der Bezeichnung kann entschieden werden, welche ListView-Instanz zur Visualisierung der Daten erstellt werden soll. Die Bezeichnung muss am „CisListPartMutable“ gesetzt werden, um den benötigten ListView-Typ identifizieren zu können.

Der Entwickler hat eine abgeleitete Klasse von der Klasse „ListView“ zu implementieren, die für die Visualisierung einer Zeile der Liste sorgt und den Datenaustausch zwischen dem Datencontainer des Modells und den GUI-Feldern realisiert (Methoden dataToUi(), dataFromUi()).

10.36 Beispielanwendung: BookListInquiry

Diese Anwendung zeigt Bücher mithilfe einer Liste an. Die Oberfläche der Anwendung ist geteilt in den Abfragebereich mit den Abfragefeldern und den Arbeitsbereich mit der Liste. In der Liste werden die auf der Datenbank gespeicherten Bücher entsprechend den Abfrageeinstellungen angezeigt. Sie sind nach Benutzerwunsch sortierbar.

Die Anwendung implementiert das Interface „ListViewCreator“ und benutzt die Containerklasse „CisListPartMutable“ für die Datencontainer der Zeilen im Modell. Sie implementiert die innere Klasse „Editor“, die von der abstrakten Klasse „ListView“ abgeleitet ist und zur Visualisierung einer Listenzeile des Modells an der GUI dient. Der folgende Screenshot zeigt die Bedienungsoberfläche:

[1]Um sich einen Überblick über die verfügbaren grafischen Elemente zu verschaffen, gibt es die Beispielanwendung https://systemURL/com.cisag.pgm.dialog.demo.WidgetSet.class

 

[2]com.cisag.app.edu.ui.HelloWorld

 

[3]Da VisualElementContainer von VisualElement abgeleitet ist, sind natürlich auch VisualElementContainer zugelassen.

 

[4]Das Border-Layout sollte daher nur dann verwendet werden, wenn die Randelemente sehr schmal sind und der Container „ausreichend“ groß ist. Als Alternative bietet sich immer das „Box-Layout“ an.

 

[5]com.cisag.pgm.gui.ConfirmationDialog

 

[6]Eigentlich ist die Meldungsklasse überflüssig, da die Meldung auch über den ihr zugeordneten Namensraum und der Meldungsnummer identifiziert werden könnte. Sie verkürzt nur die Schreibweise.

 

[7]com.cisag.app.edu.log.BookValidation

 

[8]com.cisag.pgm.datatype.NLSData

 

[9]com.cisag.pgm.datatype.CisObjectUtility

 

[10]Klasse com.cisag.pgm.gui.Table

 

[11]Durch das nachfolgende setVerticalScrollPolicy(„NEVER“) ist der Wert mehr oder weniger bedeutungslos, da die Anzahl der Zeilen automatisch an den verfügbaren Platz angepaßt wird.

 

[12]Klasse „com.cisag.pgm.gui.List“

 

Czy ten artykuł był pomocny?