Javanti
Javanti Deutsch English Spanisch

Suche:
Sitemap
     Download    Online-Demos    FAQ    Forum    Feedback Sitemap
Startseite
Javanti
Free Download!
eXchange
Support
eLearning
Entwicklung
Technische Dokumentation
Eigene Smart Elements
Wunschliste
Bug's World



Online-Demos

Eigene Smart Elements

Entwickeln eines neuen TAPElements

Der letzte - und wichtigste - Schritt zum eigenen Plug-In ist die Implementierung einer Subklasse von TAPElement. Beispielhaft werden wir die Klasse TAPMinimalElement.java implementieren, diese hier herunterladen können. Sie können diese Klasse als Grundlage für Ihre eigenen Plug-Ins nehmen und sie nach Ihren Wünschen verändern. Ein anderer Weg ist die Verwendung der Vorlage TAPTemplateElement. In dieser Datei sind alle Stellen markiert, an denen Sie noch etwas veändern oder ergänzen müssen.

Unser Beispielelement TAPMinimalElement wird die Eigenschaftsgruppe PGMinimal und die Komponente JMinimalComponent verwenden.

Als erstes deklarieren wir die Paketzugehörigkeit. Alle Plug-Ins gehören zum Paket TAPplugins:

package TAPplugins;

Danach importieren wir eine Reihe von Klassen und Schnittstellen, die wir in unserer Subklasse verwenden werden:

   import javax.swing.JComponent;
   import javax.swing.ImageIcon;
   import javax.swing.*;
   import java.awt.event.MouseMotionListener;
   import java.awt.event.MouseListener;
   import java.awt.event.MouseEvent;
   import java.util.Enumeration;
   import java.util.Vector;
   import java.util.NoSuchElementException;
   import java.io.*;
   import TAP.*;

Jetzt legen wir den leeren Klassenrumpf an. Unsere Klasse wir von TAPElement abgeleitet. Jedes jtap-Plugin wird von TAPElement abgeleitet. Wir verwenden unsere Subklasse gleichzeitig als Listener für Mausereignisse während der Laufzeit eines Kurses. Das heisst, wir implementieren auch die Schnittstellen MouseListener und MouseMotionListener. Sie legen das Verhalten der Swing-Komponente zur Laufzeit fest. Wir hätten auch gesonderte Klassen zur Implementierung dieser Schnitstellen anlegen können. Um jedoch nicht mehr Klassen als nötig zu definieren und die Laufzeit-Listener meist genau zu einem Plug-In gehören, fassen wir die Implementierung von der TAPElement-Schnittstelle und der Listner zusammen:

public class TAPMinimalElement extends TAPElement implements
    MouseListener,MouseMotionListener {
}

Man beachte: der Name der Klasse entspricht dem Namen des Plug-Ins mit einem vorangestellten "TAP" und einem angehängten "Element". Diese Namenskonvention ist wichtig, damit die jtap-Umgebung ihr Plug-In als ein solches erkennt.

Den leeren Klassenrumpf werden wir nun Schritt für Schritt zunächst mit den notwendigen Datenfeldern und schließlich mit den erforderlichen Methoden füllen.

Datenfelder

Jedes TAPElement besitzt eine Swing-Komponente. Statt eines allgemeinen Datenfeldes vom Typ JComponent in der Basisklasse anzulegen, ist es Aufgabe jeder Subklasse die gewünschte spezialisierte Swing-Komponente zu verwalten. Dies beschleunigt die Zugriffe auf Komponente, da z.B. in der update()-Methode keine Type-Casts durchgeführt werden müssen. In unserem Fall erzeugen wir ein Exemplar von JMinimalComponent:

protected JMinimalComponent myComponent = new JMinimalComponent();

Wenn Sie an das Model-View-Controller - Konzept denken, dann stellen Klassen vom Typ TAPElement das Modell dar und die Swing-Komponente den View, der das Modell graphisch darstellt. Als Controller kommen die Event-Listener zum Einsatz, die bei der Swing-Komponente registriert werden. In jtap sollen sich Elemente unterschiedlich verhalten, wenn sich jtap in der Editier- oder Laufzeit befindet. Zur Editiertzeit soll es z.B. möglich sein, Größe und Position mit der Maus zu verändern, zur Laufzeit dagegen könnten Ereignisse andere Konsequenzen haben. Es ist üblich, die Listener für die Laufzeit innerhalb der Subklasse von TAPElement zu implementieren. Für die Edit-Zeit verwendet man dagegen meist in allen Unterklassen den gleichen Listener, und zwar den StandardEditTimeListener. Dieser Listener reagiert auf Mausklicks und benachrichtigt den Objektinspektor, dass dieses Element den Fokus erhalten soll. Der StandardEditTimeListener ist im Paket TAP definiet und Sie müssen ihn nur noch in Ihre eigene Implementierung einbinden:

protected StandardEditTimeListener editListener;

Bei Bedarf ist es möglich, andere Listener für die die Editier-Zeit festzulegen. In der Regel ist dies nicht nötig.

Als nächstes wollen wir die Datenfelder für die Eigenschaftsgruppen anlegen. Unsere Subklasse erbt aus der Basisklasse bereits die Felder PGLocation location und PGSize size.Wir müssen also nur noch ein Datenfeld für unsere zusätzliche Eigenschaftsgruppe PGMinimal anlegen:

protected PGMinimal minimal;

Hinweis: Die Eigenschaften name und subName werden ebenfalls in der Basisklasse angelegt. Sie stellen einen Sonderfall dar: Sie werden nicht in eine Eigenschaftsgruppe ausgelagert (u.a. weil Namen nicht gemeinsam genutzt werden dürfen). Sie gehören aber auch nicht zu den internen Eigenschaften, da sie im Objektinspektor veränderbar sind.

Für unsere internen Eigenschaften legen wir ebenfalls Datenfelder an:

// Internal Properties:
private int internalClickCounter = 0;
private int internalClickCounterStart = 0;
private String lastMouseEvent = "no Event" ;

Man beachte, dass wir für den Mausklickzähler auch einen Startwert abspeichern, der dazu benutzt wird, nach dem Beenden eines Kurses den Zähler wieder zurück zu setzen. Für das lastMouseEvent ist der letzte Wert dagegen irrelevant, da sich der Inhalt ohnehin stets ändert.

Neben den internenen Eigenschaften gibt es weitere private Datenfelder, die nichts mit dem Typ TAPElement und der damit verbundenen semantischen Bedeutung zu tun haben. Es handel sich um Hilfsdatenfelder. Da wären zum einen xorign und yorign. Diese Werte gehören zur Implementierung der Mauslistener und werden benötigt, damit man während der Laufzeit eines Kurses ein Element verschieben kann. Das dritte Hilfsdatenfeld ist die statische Variable nameCounter. Wir benutzen sie um auf einfache (und unelegante...) Weise eindeutige Name für ein neues TAP-Element zu erzeugen. Jedesmal, wenn ein neues Element erzeugt wird, inkrementieren wir die Variable und konkatenieren sie mit einem String. Daraus ergibt sich dann ein bisher noch nicht vewendeter Name. Anmerkug: jtap wird sehr bald eine elegantere zum Erzeugen eindeutiger Name bereitstellen ;o)

Damit haben wir alle Datenfelder angelegt und kommen nun zu etwas ganz anderem.

Konstruktoren

Als erstes implementieren wir die Konstruktoren für unser Beispielelement. Dies geschieht gerade so wie bei der Implementierung der Eigenschaftsgruppen, d.h. wir legen zunächst einen öffentlichen, leeren Konstruktor an:

// empty constructor:
public TAPMinimalElement() {
}

Es folgt ein privater Konstruktor, der eine Kopie eines TAPElement-Exemplars erzeugt. Als Parameter erhält der Konstruktor alle Eigenschaftsgruppen des Elementes (auch die Gruppen, die in der Mutterklasse definiert sind), den Namen und Subnamen des Elementes und alle internen Eigenschaften:

protected TAPMinimalElement( PGLocation location,PGSize size,
          PGMinimal minimal, String name, String subName,
    int internalClickCounter, String lastMouseEvent) {

Der Konstruktor erzeugt eine neue Instanz der Klasse. Diese neue Instanz braucht einen eigenen StandardEditTimeListerner. Diesen erzeugen wir gleich zu beginn und weisen ihn dem dafür vorgesehenen Datenfeld zu:

    this.editListener = new StandardEditTimeListener(this);

Man beachte, dass wir dem StandardEditTimeListener eine Referenz auf das neu erzeugte Objekt übergibt. So ist es dem Listener nach einem Mausklick auf die Swing-Komponente möglich, dem Objektinspektor mitzuteilen, welches Element aktiviert wurde.

Wenn von einem Element eine Kopie erzeugt wird, dann befindet sich jtap im Editier-Modus. Daher fügen wir der Swing-Komponete gleich darauf den Edit-Listener hinzu:

    myComponent.addMouseListener(
               (MouseListener)this.editListener);
    myComponent.addMouseMotionListener(
               (MouseMotionListener)this.editListener);

Als nächstes müssen wir einen Bezeichner für die Kopie finden. Die Eigenschaft name bleibt unverändert, dadurch wird eine Verwandtschaft zwischen Kopie und Original signalisiert. Um dennoch eine klare Unterscheidung beider Elemente vornehmen zu können, müssen wir den subName ändern. Wir hängen einfach eine "1" an. Nicht sehr elegant, aber es funktioniert

    this.name = name;
    this.subName = subName + "1";

Die intern benutzten Eigenschaften internalClickCounter und lastMouseEvent sind eigene Erweiterungen innerhalb unserer Klasse. Es bleibt uns überlassen, ob wir sie in der Kopie berücksichtigen oder nicht.
Wir entscheiden uns dafür, die Werte des Originals zu kopieren:

    // copy internal properties:
    this.internalClickCounter = internalClickCounter;
    this.lastMouseEvent = lastMouseEvent;

Für die veröffentlichten Eigenschaftsgruppen haben wir hingegen keine Entscheidungsfreiheit. Wir müssen jede von uns festgelegte Gruppe für unser Element anlegen. Die logische Bedeutung der Element-Kopie verlangt von uns, dass wir die Eigenschaftsgruppen des Originals kopieren oder benutzen.
Die Vorgehensweise beim Prüfen, ob eine Gruppe kopiert (wenn sie non-shared ist) oder gemeinsam genutzt (wenn sie shared ist), ist für alle Gruppe gleiche. Der Prozess wurde daher in die Defaultimplemenierung von TAPPropertyGroup assignGroup(TAPPropertyGroup src) ausgelagert. Dieser Methode wird eine Eigenschaftsgruppe des Originals übergeben. Dabei handelt es sich um die Gruppen, die der Konstruktor als Parameter erhält. assignGroup(...) liefert nun eine Kopie der Gruppe oder die gleiche Gruppe zurück, wenn diese gemeinsam genutzt werden kann. In diesem Fall erhöht assignGroup auch den Referenzzähler der Eigenschaftsgruppe.
assignGroup(...) liefert den allgemeinen TAPPropertyGroup-Typ zurück, den wir in den Untertyp unserer speziellen Implementierung casten müssen:

    this.location = (PGLocation) assignGroup(location);
    this.size = (PGSize) assignGroup(size);
    this.minimal = (PGMinimal) assignGroup(minimal);

Am Ende des Konstruktor sezten wir noch ein isRuntime-Flag (geerbt aus TAPElement) auf false, da Kopien grundsätzlich zur Edit-Zeit geschehen:

    isRuntime = false;
} // end of construtctor

Basis-Methoden

Damit ist der Konstrutor fertig implementiert. Er wird aufgerufen von der Methode getACopy():

public TAPElement getACopy(){
    return
    new TAPMinimalElement(location,size, minimal, name, subName,
    internalClickCounter,lastMouseEvent);
}

Ähnlich wie der private Konstruktor zum Erzeugen von Kopien arbeitet die Methode init() für ganz neue Objektinstanzen. Diese Methode wird direkt nach dem Erzeugen eines neuen Exemplares aufgerufen. Da hier jedoch keine Kopie erzeugt wird, sondern ein vollkommen neues Element, werden alle Eigenschaftsgruppen und Eigenschaftwerte neu erzeugt. Als erstes belegen wir die Eigenschaft name mit einem eindeutigen Namen unter Verwendung unseres Hilsdatenfeldes nameCounter. Den Subnamen können wir frei wählen:

public void init() {
    // insert here a name-finder method
    name = "minimalElement" + String.valueOf(nameCounter++);
    subName = "sub_0";

Wie bereits beim Konstruktor gesehen, müssen wir einen Edit-Listener erzeugen und diesen bei der Swing-Komponente registrieren. Auch das Flag isRuntime wird auf falsch gesetzt.

    editListener = new StandardEditTimeListener(this);
    // add the edit-mode-listeners to the component:
    myComponent.addMouseListener((MouseListener)editListener);
    myComponent.addMouseMotionListener((MouseMotionListener)editListener);
    isRuntime = false;

Und jetzt endlich kommt etwas neues. Statt bestehene Eigenschaftsgruppen zu kopieren, erzeugen wir neue Exemplare:

    location = new PGLocation();
    size = new PGSize();
    minimal = new PGMinimal();
} // end of init()

Eine sehr einfache Methode der Schnittstelle TAPElement ist getComponent(). Sie liefert die Swing-Komponente zurück, in der unser Element visualisiert wird. In diesem Fall also unsere JMinimalComponent:

public JComponent getComponent() {
    return myComponent;
}

Bisher haben wir jedoch nur Programmcode geschrieben, der diese Swing-Komponente erzeugt. Wir möchten aber das Aussehen der Komponente dem aktuellen Zustand unseres TAPMinimalElements anpassen. Dazu gehört zum einen, dass wir die Positionierung und Größe der Komponente aufgrund unserer veröffentlichten Eigenschaften x,y, width und height festsetzen können. Zum anderen wollen wir aber auch die Eigenschaften aus PGMinimal berücksichtigen und die internen Daten internalClickCounter und lastMouseEvent innerhalb der Komponente als Text ausgeben.

Als erstes prüfen wir, ob die Komponente überhaupt sichtbar sein soll. Nur dann müssen wir die (manchmal) rechenintensiven Eigenschaftsanpassungen vornehmen. Das Setzen von Position und Größe können Sie an die Methode TAPElement.updateBounds(..) delegieren. Da die Eigenschaftsgruppen PGLocation und PGSize in jeder Unterklasse von TAPElement vorhanden sind, kann diese Hilfsmethode der Basisklasse auch auf die Werte zurückgreifen. Sie müssen allerdings die Swing-Komponente als Parameter übergeben, da diese ja spezifisch für jede Unterklasse ist. Sie können die Größe und Position des Elementes auch selbst berechnen und setzen, wenn sie ein spezielles Verhalten ihres Elementes erreichen wollen. Sie würden dann die Methode myComponent.setBounds(...) mit den gewünschten Parametern aufrufen.

public void update() {
    if (location.getVisibleCurr() || !this.isRuntime) {
        myComponent.setVisible(true);
        updateBounds(myComponent);

Sie erinnern sich sicherlich, dass wir für unsere Komponente mehrere set-Methoden geschrieben haben, um die Eigenschaften der Komponente zu verändern. Diese Methoden können wir nun aufrufen, um - basierend auf den aktuellen Eigenschaftswerten unseres Elements - die Eigenschaften der Komponente zu aktualisieren:

        myComponent.setInfoString( minimal.getStatement() + lastMouseEvent);
        myComponent.setOffset(minimal.getOffset());
        myComponent.setBold(minimal.getIsBold());
        myComponent.setClickCounter(String.valueOf(internalClickCounter));
        myComponent.repaint();
    } else myComponent.setVisible(false);
} // end update()

Methoden als Nachrichtenempfänger

Als nächstes betrachten wir Methoden, die ein TAPElement benachrichtigen, wenn bestimmte Ereignisse in der jtap-Umgebung auftreten. Zu diesen Ereignissen gehören das Starten und Beenden eines Kurse sowie das Sichtbar (aktiv) und Unsichtbar (inaktiv) werden eines Elementes. Ein Element ist dann aktiv, wenn es sich auf einer Folie befindet, die aktuell auf der jtap-Tafel angezeigt wird.

Die Methode startTAP() wird immer aufgerufen, wenn eine Präsenation gestartet wird. Da wir verhindern wollen, dass wir auf ein Starten reagieren wollen, wenn sich die Präsentation bereits im Runtime-Modus befindet, handeln wir nur, wenn das entsprechende Flag isRuntime noch nicht gesetzt ist. Am Ende der Methode setzen wir isRuntime auf jeden Fall auf true, denn die Präsentation läuft jetzt - egal ob sie wirklich gerade gestart wurde oder bereits vorher lief.

public void startTAP() {
    if (!isRuntime) {

Dann benachrichtigen wir unsere alle verwalteten Eigenschaftsgruppen darüber, dass die Präsentation gestartet wurde und sie die aktuellen Werte speichern müssen:

        location.restartRuntime();
        size.restartRuntime();
        minimal.restartRuntime();

Wir tauschen die EditTime-Listener gegen die Runtime-Listener der Komponente aus. Der editListener wird als MouseListener und MouseMotionListern abgemeldet. Da unsere Klasse selbst die Schnittstellen MouseListener und MouseMotionListener implemeniert, registrieren wir sie für beide Ereignisarten bei der Komponente:

        myComponent.removeMouseListener(editListener);
        myComponent.removeMouseMotionListener(editListener);
        myComponent.addMouseListener(this);
        myComponent.addMouseMotionListener(this);

Schließlich passen wir die grafische Ausgabe der Komponente noch einmal an und beenden diese Methode:

        requestUpdate();
        update();
    } // end if (!isRuntime)
    isRuntime = true;
}// end startTAP()

Das Gegenstück ist stopTAP(). Hier geschehen genau die entgegengestzen Arbeitsschrittel. Wir stellen sicher, dass wir uns auch wirklich im Runtime-Modus befinden und benachrichtigen dann unsere Eigenschaftsgruppen darüber, dass die Präsenatation zu Ende ist. loacation, size und minimal sind damit aufgefordert, die gesicherten Ausgangswerte vom Start der Präsentation wieder als aktuelle Werte zu benutzen und eventuelle Wertänderungen damit rückgängig zu machen.
Als MouseListener und MouseMotionListener wird unsere Klasse wieder von der Komponente entfernt. Dafür wird der editListener registriert.

public void stopTAP() {
    if (isRuntime) {
        internalClickCounter = internalClickCounterStart;
        location.resetEdittime();
        size.resetEdittime();
        minimal.resetEdittime();
        myComponent.removeMouseListener(this);
        myComponent.removeMouseMotionListener(this);
        myComponent.addMouseListener(editListener);
        myComponent.addMouseMotionListener(editListener);
        update();
    } // end if (isRuntime)
    isRuntime = false;
} // end stopTAP()

Wir kommen nun zu startElement() und stopElement().

startElement() wird aufgerufen, sobald die Folie mit diesem Element auf den virtuellen Overhead-Projektor gelegt wird - mit anderen Worten: das Element wird jetzt aktiv. Reservieren Sie hier alle teuren Resourcen (z.B. Speicher, Rechenzeit, Netzwerkverbinungen,...), die Sie nur dann brauchen, wenn das Element wirklich sichtbar wird. Dies ist ein günstiger Zeitpunkt, um z.B. rechenintensive Animationen zu starten. In unserem Beispielelement geschieht dagegen nichts:

public void startElement() {}

stopElement() wird aufgerufen, sobald die Folie mit diesem Element vom virtuellen Overhead-Projektor herunter genommen wird - mit anderen Worten: das
Element ist nicht mehr aktiv / sichtbar.Geben Sie hier alle teuren Resourcen (z.B. Speicher, Rechenzeit, Netzwerkverbinungen,...) frei, wenn Sie diese nur dann brauchen, wenn das Element wirklich sichtbar wird. Dies ist ein günstiger Zeitpunkt, um z.B. rechenintensive Animationen pausieren zu lassen - die werden nämlich gar nicht mehr angezeigt.

In unserem Beispielelement ist dies nicht nötig, auch diese Methode bleibt leer:

public void stopElement() {}

Als nächstes soll unser Element noch auf Nachrichten reagieren, die ihm von anderen Elementen gesendet werden. Eine Nachricht kann durch den TCL-Befehl "tell" an ein anderes Element gesendet werden. Seine Syntax ist diese:

tell zielLayer zielElement nachricht

Beispiel:

tell layer1 elementName.subName "any kind of message"

Die Nachricht kann eine beliebig einfache oder komplexe Nachricht sein. Nachrichten können dazu genutzt werden, den Zustand eines Elementes zu verändern, oder um Operationen auf dem Element auszuführen. Eine Nachricht kann auch eine Anfrage sein, die einen Ergebnis-String zurückliefert.

Interessante Beispiele für Nachrichten sind hier zu finden.

Für unsere Beispielimplementation wollen wir uns einmal wieder auf das Nötigste beschränken. Wir interpretieren die Nachricht nicht als Skript oder Anweisung sondern geben Sie nur einfach innerhalb unserer Komponente aus. Zufällig haben wir bereits ein Datenfeld, in dem wir die Nachricht zwischenspeichern können: lastMouseEvent. Eigentlich wird darin ja das letzte Mausereignis abgelegt. Es ist aber durchaus zulässig das Empfangen einer Nachricht auch als ein Ereignis aufzufassen. Wir belegen also lastMouseEvent mit der gerade empfangenen Nachricht und rufen update() auf, damit die Zustandsänderung auch sichtbar gemacht wird:

public String tell (String message, String layerName) {
    lastMouseEvent = "tell: " + message ;
    requestUpdate();
    update();
    return "wrote message to screen";
}

Der tell-Befehl gibt uns außerdem die Möglichkeit, eine Antwort auf die empfangene Nachricht zu geben. In unserer Beispielimplementierung geben wir nur einen nutzlosen Infotext zurück.
Für komplexere Nachrichten ist hier eine sinnvollere Antwort zu wählen. Die Nachricht wird dann zu einer Anfrage, die bearbeitet wird. Anfragen könnten z.B. Skripte, Anweisungen oder Ausdrücke sein. Das ausgewertete Ergebnis sollte dann zurück geliefert werden.

Informationen über das Plug-In

Die jtap-Umbebung verlangt von Ihrer Plug-In-Implementierung ein paar aussagekräftige Informationen, damit ihr eigenes Element eingebunden werden kann.

Die Methode getElementType() ist klein aber essentiel. Sie liefert einen String der den Elementtyp benennt. Dies ist kein Java-Datentyp sondern eine Bezeichnung für eine TAPElement-Klasse. Als Bezeichnung lassen wir das "TAP" und "Element" aus unserem Klassennamen weg und erhalten als ElementType "Minimal".
Unter dieser Bezeichnung erscheint unser Plug-In später im Control-Panel zur Auswahl. Diese Bezeichnung wird auch in einer Plug-In-Tabelle als Schlüssel benutzt, der festlegt, welches Element erzeugt wird. Vermeiden Sie also doppelte Plug-In-Bezeichner, zum Beispiel indem Sie ihren Namen zum Bestandteil des Bezeichners machen. Da unsere Klasse "TAPMinimalElement" heißt, liefern wir also "Minimal" zurück:

public String getElementType() { return "Minimal"; }

Mit der Methode getFamilyName() können Sie mehrere Plug-Ins zusammen gruppieren. Da wir als Beispiel nur ein einziges Plug-In entwickelt, macht das Überschreiben dieser Methode für uns keinen Sinn. Mehr Informationen über getFamilyName() finden Sie in der Vorlage TAPTemplate.java

Von unsererm Plug-In wird außerdem velangt, dass eine Versions-Nummer als String zurück geliefert wird. Dies ist für die Unterscheidung der unterschiedlichen Versionen essentiell:

public String getVersion() { return "v1_8.3.02" ; }

Sie haben die Möglichkeit, Ihr Plug-In durch ein eigenes Gif-Icon in jtap zu repräsentieren. Dazu überschreiben wir die Methode getIconRepresentation und liefern ein ImageIcon zurück:

public ImageIcon getIconRepresentation() {
    Class c = TAPMinimalElement.class;
    return new ImageIcon(c.getResource("icons/mini.gif"));
}

Eigenschaftsgruppen

Der Objektinspektor von jtap muss eine Möglichkeit haben, zu erfahren, welche Eigenschaftsgruppen Ihr Plug-In bereitstellt. Ähnlich wie bei der Implementierung der Eigenschaftsgruppen selbst, gibt es hier die Methoden getPropertyGroupCount() und propertyGroupAt(). Die erste Methode liefert die Anzahl der verwalteten Eigenschaftsgruppen, inklusiver einer fiktiven Gruppe PGName. Diese wird benötigt, um die Namen des Elementes im Objektinspektor so zu behandeln, wie eine normale Eigenschaftsgruppe. Wir können dabei mit PGName.getSingletonPGName() auf ein Singleton zurückgreifen. Die Methoden werden für unser Beispiel wie folt implementiert:

public int getPropertyGroupCount() {
    return 4 ; // PGName, PGLocation , PGSize , PGMinimal
}

public TAPPropertyGroup propertyGroupAt(int index)
       throws NoSuchElementException    {
    switch (index) {
        case 0: return PGName.getSingletonPGName();
        case 1: return location;
        case 2: return size;
        case 3: return minimal;
        default: throw new NoSuchElementException(
                 "PGLocation contains only three property groups!");
    } // end switch
} // end nextElement

Im Objektinspektro von jtap ist es außerdem möglich, eine komplette Eigenschaftsgruppe auszutauschen. Dies geschieht, wenn im ObjektInspector für eine Gruppe der Status von "shared" auf "non-shared" gesetzt wird oder eine andere "shared" Gruppe für unser Element festgelegt wird.
Das Austauschen einer Eigenschaftsgruppe geschieht mit der Methode setPropertyGroup(TAPPropertyGroup newGroup), der eine beliebige Gruppe übergeben werden kann. In dieser Methode wird mit newGroup eine bisher genutzte Eigenschaftsgruppe ersetzt. Sie müssen anhand des Gruppennamens newGroup.getPropertyGroupName()) entscheiden, welches Gruppe ersetzt werden soll. Dabei wird die newGroup dem dafür vorgesehenen Datenfeld zugewiesen. * Für jede unserer Eigenschaftsgruppen, die wir in dieser Subklasse verwalten, muss es also ein if-Statement geben:

public void setPropertyGroup(TAPPropertyGroup newGroup){
    if (newGroup.getPropertyGroupName().equals("Location"))
        location = (PGLocation) newGroup;
    if (newGroup.getPropertyGroupName().equals("Size"))
        size = (PGSize) newGroup;
    if (newGroup.getPropertyGroupName().equals("Minimal"))
        minimal = (PGMinimal) newGroup;
}

Speichern des Elementes

Das Speichern des Elementes wird automatisch von der Basisklasse übernommen. Diese delegiert die Arbeit an die verwalteten Eigenschaftsgruppen. Die Basisklasse kann allerdings keine internen Eigenschaften speichern, da diese ja spezifisch für die jeweilige Subklasse sind. Daher ruft die Basisklasse die Methode saveInternalData auf. In dieser Methode haben wir die Gelegenheit beliebige Namen-Werte-Paare in die Datei zu speichern. Dazu rufen wir die Methode TAPElement.writeInternalData(...) auf und übergeben ihr den FileStream sowie den Datenname und Datenwert als String.

public void saveInternalData(Writer outputFile) {
    writeInternalData(outputFile,"internalClickCounter",
                      String.valueOf(internalClickCounter));
}

Für jedes Datenpaar, dass wir auf diese Weise in den FileStream geschrieben haben, wird beim erneuten Laden des jtap-Kurses für unser Element die Methode initInternalData (String dataName, String dataContent) aufgerufen. In dataName und dataContent befindet sich jeweils ein Name und ein Wert, die wir vorher mit saveInternalData gspeichert hatten. Wir können nun über if-Statements für die passenden Namen die dazugehörigen Datenzuweisungen vornehmen:

public void initInternalData(String dataName, String dataContent) {
    if (dataName.equals("internalClickCounter")) {
        internalClickCounterStart =
                    Integer.valueOf(dataContent).intValue();
        internalClickCounter = internalClickCounterStart;
    }
}

Implementierung der MouseListener

Damit ist der Typ TAPElement vollständig implmentiert. Wir wollen nun noch die MouseListener implemnetieren. Dies ist nicht weiter aufwendig, da wir in unserem Beispiel auf Mausereignisse ja nicht komplex reagieren. Für die MouseListener-Methoden setzen wir den Info-String lastMouseEvent neu und rufen anschließend update() auf, damit die Änderung auch visuell dargestellt wird.
In der Ereignismethode mouseClicked(..) inkrementieren wir außerdem unseren internalClickCounter.
Die Methoden mousePressed(..) und mouseDraged(..) ändern unter Verwendung der Hilfsvariablen xorign und yorign die Eigenschaften x und y unseres Elementes, wenn unsere Komponente auf der Bühne mit der Maus gezogen wird. Auch hier muss wieder eine Umrechnung von den absoluten Werten, die uns ein MouseEvent-Objekt liefert, in die relativen Werte bezogen auf die aktuelle Fenstergröße umgerechnet werden.

// implentation of MouseListener:

public void mouseClicked(MouseEvent e) {
    lastMouseEvent = "mouse was clicked.";
    internalClickCounter++;
    update();
}

public void mouseEntered(MouseEvent e) {
    lastMouseEvent = "mouse entered component.";
    update();
}

public void mouseExited(MouseEvent e) {
    lastMouseEvent = "mouse exited component.";
    update();
}

public void mousePressed(MouseEvent e) {
    lastMouseEvent = "mouse was pressed.";
    // to drag the element:
    if (location.getDragableCurr()) {
        xorign = e.getX();
        yorign = e.getY();
    }
    update();
}

public void mouseReleased(MouseEvent e) {
    lastMouseEvent = "mouse was released.";
    update();
}

public void mouseDragged(MouseEvent e) {
    // to drag the element:
    if (location.getDragableCurr()) {
        setPropertyRemote("x",String.valueOf(
           (e.getX() + xorign)/TAPBoard.currentBoard.getWidth() * 100) );
        setPropertyRemote("y",String.valueOf(
           (e.getY() + yorign)/TAPBoard.currentBoard.getHeight() * 100) );
        xorign = e.getX() + xorign;
        yorign = e.getY() + yorign;
    } // end if dragable
}

public void mouseMoved(MouseEvent e) {
}

Hinweis: Sie können Ihrem Element beliebig komplexe Verhaltensweisen geben, indem Sie innerhalb der Listener auf Ereignisse in der Ausgabe-Komponente reagieren. Außer den Mausereignissen können auch alle anderen Events beobachtet und behandelt werden.
Wenn Sie auf ein Ereignis mit einem TCL-Script reagieren möchten, welches der Anwender selbst defineren kann, dann müssen sie für diese(s) Script(e) eine oder mehrere Eigenschaften vom Typ TAPPropertyGroup.SCRIPT_TYPE anlegen. Das Script selbst ist String-Object, dass sie dem TCL-Interpreter von TAP übergeben können. Beispiele finden Sie in den TAPElement-Subklassen des TAP-Pakets. Beachten Sie auch die Möglichkeit von der Klasse TAPMouseAdapterElement abzuleiten. Diese Klasse stellt behandelt bereits die MouseEvents und verwaltet die Eigenschaftsgruppe PGMouseEvents.

WICHTIGER HINWEIS: Da alle Listener, die Sie für Ihr Element implementieren, in der Regel eine Referenz auf das Element selbst enhalten, können Sie innerhalb der Listener-Klasse auch alle Methoden des Elementes aufrufen. Davon sollten sie aber keinen Gebrauch machen!!! Stattdessen verwenden Sie bitte ausschließlich

  • setPropertyRemote(String propertyName, String propertyValue) zum Setzen der veröffentlichten Eigenschaften
  • tellRemote(String message)
  • Methoden nach einer speziellen Konvention, die hier beschrieben ist
Die Einhaltung dieser Regeln ist ausgesprochen wichtig, da ihr Element sonst nicht mehr in verteilten jtap-Systemen (also auf mehreren Rechnern synchron ablaufend) funktioniert. Würden Sie aus einem Event-Listener heraus z.B. direkt setProperty(...) aufrufen, so würde die Eigenschaft nur lokal verändert werden. Durch Aufruf von setPropertyRemote(..) erreichen Sie dagegen, dass alle teilnehmenden jtap-Programme von der Änderung erfahren. setPropertyRemote(..) sorgt dann automatisch dafür, dass setProperty(..) aufgerufen wird.

Fertig!

So, nun haben wir unser eigenes Element selbst geschrieben. Es muss nun nur noch kompiliert werden. Dabei muss das TAP-package natürlich in den classpath unseres Compilers aufgenommen werden. Der Compiler muss die Klasse TAPElement finden, damit er eine Ableitung (unser TAPMinimalElement) von ihr generieren kann.

Nach der Kompilierung unserer neuen Klassen werden die class-Files in das Verzeichnis TAPplugins der jtap-Installation kopiert. Beim nächsten Start von jtap steht das neue Element zur Verfügung. Es sei darauf hingewiesen, dass Hilfsklassen, verwendete Klassen und Komponenten nicht im Verzeichnis TAPplugins liegen müssen. Diese Regel gilt nur verbindlich für die Element- und PropertyGroup-Klassen.

  Impressum

Druckversion  |  Nach oben