Videoautomat - Den Videobestand anzeigen

Im folgenden Kapitel soll der zuvor konstruierte Videobestand für den Kunden sichtbar gemacht werden. Dazu wird das Standard-FormSheet von VideoAutomat durch eine Angebotstabelle ersetzt.


Eine Angebotstabelle erstellen

Wie im Technischen Überblick erläutert wird, kann auf einem Display ein FormSheet und/oder ein MenuSheet angezeigt werden. Für Ersteres existieren diverse Spezialisierungen, wie z.B. das SingleTableFormSheet. Dieses kann den Inhalt einer Datenstruktur des Frameworks - eines Katalogs, Bestands oder Datenkorbs - anzeigen.

Der Videobestand soll als Begrüßungsbildschirm des Automaten angezeigt werden, so dass die Kunden sofort einen Blick auf die aktuelle Filmauswahl haben, ohne sich vorher anmelden zu müssen. Da das Standard-FormSheet eines SalesPoint immer dann angezeigt wird, wenn kein Prozess auf diesem läuft, bietet es sich an, eben dieses durch die Begrüßungsanzeige zu ersetzen. Dies geschieht durch das Überschreiben der Methode getDefaultFormSheet() in der Klasse VideoAutomat.

    protected FormSheet getDefaultFormSheet() {
        FormSheet fs = 
                SingleTableFormSheet.create(
                    "Please log on!",
                    VideoShop.getVideoStock(),
                    null,
                    false,
                    new TEDVideoStock());
        return fs;
    }
		

Es bedarf keiner angepassten, neuen Klasse von SingleTableFormSheet, da diese bereits diverse statische create()-Methoden zur Verfügung stellt, die nahezu alle benötigten Tabellen erzeugen und zurückgeben können. Wie der Inhalt der jeweiligen Datenstruktur angezeigt wird, bestimmt die übergebene Instanz von TableEntryDescriptor (siehe Abschnitt Der TableEntryDescriptor). Die boolesche Variable entscheidet darüber, ob nicht Verfügbares, d.h. Katalogreferenzen mit der Anzahl null, angezeigt werden soll. Die Instanz der Klasse UIGate kann man an dieser Stelle ignorieren. Sie wird nur dann gebraucht, wenn die Tabelle innerhalb eines Verkaufsprozesses angezeigt werden soll. Der zu übergebende CountingStock wird in diesem Fall natürlich der Videobestand sein.

Wie die Buttons und das Erscheinungsbild verändert werden können zeigt der Abschnitt Der FormSheetContentCreator. Doch zunächst wird der übergebene TableEntryDescriptor näher erklärt.


Der TableEntryDescriptor

Ein SingleTableFormSheet bildet die einzelnen Elemente der jeweiligen Datenstruktur auf die Zeilen, deren Attribute auf die Spalten ab. Die Abbildung 4.1 zeigt dies anhand eines Beispiels.



Beispiel eines SingleTableFormSheet
Abbildung 4.1: Beispiel eines SingleTableFormSheet

Die Frage ist jedoch, woher die Information stammt, welche Attribute des jeweiligen Datums wie angezeigt werden sollen. Die Antwort liefert das Interface TableEntryDescriptor (TED). Eine Klasse mit dem Verhalten eines TED kann unter anderem die Spaltenanzahl, die Überschriften, den Inhalt der Spalten, sowie die Art der Formatierung über verschiedene Methoden zurückgeben. Die Klasse AbstractTableEntryDescriptor reduziert den Aufwand der Implementation eines TEDs, da in ihr diverse Methoden vordefiniert sind. Eine neu erzeugte Klasse TEDVideoStock soll von AbstractTableEntryDescriptor erben.

package videoautomat;
public class TEDVideoStock extends AbstractTableEntryDescriptor {
    private String[] cNames = { "Name", "Price", "Count" };
    private Class<?>[] cClasses = { String.class, NumberValue.class, Integer.class };
    public int getColumnCount() {
        return 3;
    }
    public String getColumnName(int nIdx) {
        return cNames[nIdx];
    }
    public Class<?> getColumnClass(int nIdx) {
        return cClasses[nIdx];
    }
    public Object getValueAt(Object oRecord, int nIdx) {
        CountingStockTableModel.Record r = (CountingStockTableModel.Record) oRecord;
        switch (nIdx) {
            case 0 :
                return r.getDescriptor().getName();
            case 1 :
                return ((QuoteValue) r.getDescriptor().getValue()).getOffer();
            case 2 :
                return new Integer(r.getCount());
        }
        return null;
    }
}
		

Die erste Methode liefert die Spaltenanzahl. Die jeweilige Überschrift wird durch die Methode getColumnName(int idx) ermittelt. Dazu wird der Spaltenindex übergeben. Beispielsweise erhält man die Überschrift der ersten Spalte durch den Aufruf getColumnName(0). Informationen über den Typ der anzuzeigenden Daten und damit verbunden über die nötige Art der Formatierung liefert getColumnClass(int idx). Die einzutragenden Daten gibt getValueAt(Object oRecord, int nIdx) zurück. Der zuletzt genannten Methode muss der betreffende Zeileneintrag übergeben werden. Der Typ dieses Zeileneintrags hängt vom verwendeten Tabellenmodell ab, dieses wiederum von der Datenstruktur, die angezeigt werden soll. Ein Blick in das SalesPoint-API auf die Subklassen von AbstractTableModel genügt, um das jeweils verwendete Tabellenmodell und damit den Typ der Einträge zu bestimmen.

In diesem Fall soll ein CountingStock angezeigt werden, entsprechend handelt es sich bei den Tabelleneinträgen um den Typ CountingStockTableModel.Record. Wie die Attribute, die angezeigt werden sollen, über diese Klasse zurückgegeben werden können, ist im obigen Beispiel des TEDVideoStock zu erkennen bzw. in der API nachzuschlagen.

Nach erfolgter Kompilierung und Ausführung des Programms kann das Ergebnis betrachtet werden.

Auffällig ist die Anzeige der Preise. Es wird zwar der korrekte Wert in Cent ausgegeben, jedoch wäre es wünschenswert die allgemein übliche Währungsformatierung zu nutzen. Um diesen Schönheitsfehler zu korrigieren, ist zuvor die Definition einer Währung notwendig. Dieser Vorgang wird im Abschnitt Das Geld beschrieben. Weiterhin fällt auf, dass ein Ok- und ein Cancel-Button angezeigt werden, die zuvor nicht definiert wurden. Es handelt sich um die Standard-Buttons der Klasse FormSheet, die an SingleTableFormSheet automatisch weitervererbt werden.


Der FormSheetContentCreator

Die Anordnung der Elemente eines Formulars muss in einem FormSheetContentCreator vorgenommen werden, der dann dem Formular zugefügt wird. Der Sinn dieser Klasse besteht darin, das Formular serialisierbar und damit speicherfähig zu machen. Wird die Anpassung ohne einen derartigen Erzeuger vorgenommen, kann es sein, dass das FormSheet nach dem Speichern und Laden der Anwendung unvollständig angezeigt wird oder nicht korrekt arbeitet. Es müssen also alle Änderungen an einem FormSheet durch einen FormSheetContentCreator durchgeführt werden.

Zur Anpassung der Buttonleiste wird dementsprechend ein FormSheetContentCreator in der Erzeugermethode definiert und dem Tabellenformular hinzugefügt. FormSheetContentCreator ist eine abstrakte Klasse und fordert zur Ableitung der Klasse und Implementierung der Methode createFormSheetContent(FormSheet fs) auf. Die Implementierung könnte an dieser Stelle als anonyme Klasse erfolgen, jedoch bietet eine eigene Klasse einige Vorteile. Es wird die Trennung zwischen Anwendungslogik und Oberfläche verbessert, was später besonders bei den Prozesses deutlich wird. Außerdem kann dieser FormSheetContentCreator so für mehrere FormSheets wiederverwendet werden. Später im Tutorial wird gezeigt, wie man solch einen FormSheetContentCreator konfigurieren kann, um so beispielsweise andere Aktionen für die Buttons zu definieren, die Darstellung leicht anzupassen oder schon Werte einer Eingabemaske vorzugeben.

package videoautomat.contentcreator;
public class StartFSContentCreator extends FormSheetContentCreator {
    public static final int FB_LOGON = 1;

    public static final int FB_REGISTER = 2;

    protected void createFormSheetContent(FormSheet fs) {
        fs.removeAllButtons();

        fs.addButton("Login", FB_LOGON , new RunProcessAction(new SaleProcessLogOn()));

        fs.addButton("Register", FB_REGISTER, new RunProcessAction(new SaleProcessRegister()));
    }

}
		

Die Methode removeAllButtons() tut das, was der Name verspricht, während addButton(String name, int id, sale.Action a) einen Formbutton erzeugt und dem Formular hinzufügt. Formbutton ist die Framework-Version eines Buttons und besitzt eine Beschriftung, eine ID zur eindeutigen Unterscheidung und eine mit diesem Knopf assoziierte Aktion. Die ID kann unter anderem dazu verwendet werden, einen Button außerhalb der Deklaration des FormSheet referenzieren zu können. Dies ermöglicht z.B. nachträglich die Aktion des Buttons zu verändern. Es sollten jedoch alle Änderungen der Darstellung an einer Stelle erfolgen, um die Übersicht behalten zu können und den Quelltext les- und wartbarer zu halten, besonders wenn mehrere Teammitglieder am gleichen Programm arbeiten. Hier wird beispielsweise die RunProcessAction genutzt, um den Code zum Starten eines Prozesses wiederzuverwenden. Dies ist keine Framework-Klasse und sieht folgendermaßen aus:

package videoautomat.contentcreator.stdactions;
public class RunProcessAction implements Action {
    private static final long serialVersionUID = -8681932162005178429L;
   private SaleProcess process;

   private DataBasket basket;
   public RunProcessAction(SaleProcess process) {
      this.process = process;
   }
   public RunProcessAction(SaleProcess process, DataBasket basket)
   {
       this.process = process;
       this.basket = basket;
   }
   public void doAction(SaleProcess saleProcess, SalesPoint salePoint) throws Throwable {
      if(basket != null)
          salePoint.runProcess(process, basket);
      else
          salePoint.runProcess(process);
   }

}
                

Sie ist im Package videoautomat.contentcreator.stdactions; abgelegt und implementiert das Interface Action. Im ersten Konstruktor wird die Instanz des Prozesses übergeben, der durch diese Aktion gestartet werden soll. Im zweiten Konstruktor wird zusätzlich ein DataBasket übergeben, der beim Starten des Prozesses ihm zugewiesen wird. Das Starten erfolgt in der doAction Methode, die als Parameter den verwendeten SaleProcess und den SalesPoint übergeben bekommt. Somit kann auf diesem SalesPoint die runProcess Methode ausgeführt werden. Aufgrund dieser Parameterisierbarkeit, kann diese Klasse immer genutzt werden, wenn ein Prozess über eine Aktion gestartet werden soll. Man spart sich also jedesmal als anonyme Klasse das Action-Interface zu implementieren und die Anweisungen zum Starten jedesmal erneut zu schreiben.

Hinweis: Die Anordnung der Buttons ist durch die Reihenfolge ihres Hinzufügens bestimmt.

Nun muss der FormSheetContentCreator dem FormSheet hinzugefügt werden. Entsprechend sieht die getDefaultFormSheet Methode im Verkaufstand aus:

    protected FormSheet getDefaultFormSheet() {
        FormSheet fs = 
                SingleTableFormSheet.create(
                    "Please log on!",
                    VideoShop.getVideoStock(),
                    null,
                    false,
                    new TEDVideoStock());
        fs.addContentCreator(new StartFSContentCreator());
        return fs;
    }
		

Die Klassen SaleProcessLogOn und SaleProcessRegister werden erst in den Abschnitten Deklaration eines SaleProcess (Login) bzw. Deklaration des Prozesses (Registrierung) eingeführt. Um das Beispiel zum Laufen zu bringen ist es aber in Ordnung, statt der Instanzen zunächst null zu übergeben (im FormSheetContentCreator). Nach erneuter Übersetzung und Ausführung kann die neue Buttonleiste bewundert werden.


previous Videokatalog und -bestandDie Nutzerverwaltung next



by Thomas Ryssel