3.12.2. Die Schnittstelle ICommand

Eine Command Action teilt eine Action in den beschreiben Anteil wie Label Text, Tooltip Text oder Icon, und die Business Logik auf. Ein ICommand stellt dabei die Business Logik dar. Diese hat drei Aspekte:

Die Schnittstelle ICommand wird häufig selbst implementiert, um die Business Logik abzubilden. Diese hat die folgenden Methoden:

    ICommandExecutor getCommandExecutor();

    IExceptionHandler getExceptionHandler();
    
    IEnabledChecker getEnabledChecker();

Dabei kann jede der Methoden null zurückliefern, wenn der jeweilige Aspekt nicht unterstützt wird (einen Enabled Checker oder Exception Handler anzubieten, ohne einen Executor zu haben ist allerdings nicht unbedingt sinnvoll).

Die Schnittstelle ICommandExecutor

Die Schnittstelle ICommandExecutor hat die folgende Methode:

    void execute(IExecutionContext executionContext) throws Exception;

Diese wird aufgerufen, wenn der Command ausgeführt werden soll. Die Schnittstelle IExecutionContext liefert dabei die folgenden Methoden:

    IAction getAction();
    
    IWidget getSource();
    
    <VALUE_TYPE> VALUE_TYPE getValue(final ITypedKey<VALUE_TYPE> key);

Damit kann man eine Referenz auf die auslösende Action, das Widget, für welches die Action ausgeführt wurde, und weitere Properties erhalten. Über die ausführende Action kann man sich Beispielsweise das Action Label und Icon beschaffen, um diese etwa in einem Dialog anzuzeigen. Das folgende Beispiel soll das verdeutlichen:

  1  public final class ExampleExecutor implements ICommandExecutor {
  2  
  3      @Override
  4      public void execute(final IExecutionContext executionContext) throws Exception {
  5          final IAction action = executionContext.getAction();
  6          final String title = action.getText();
  7          final IImageConstant icon = action.getIcon();
  8          final String message = "Execution was successful";
  9          MessagePane.showInfo(title, icon, message);
 10      }
 11  
 12  }

Das dies ein häufiger Anwendungsfall ist, unterstützt die Accessor Klasse MessagePane auch das direkte Übergeben des executionContext. Die folgende Implementierung hat daher den gleichen Effekt:

  1  public final class ExampleExecutor implements ICommandExecutor {
  2  
  3      @Override
  4      public void execute(final IExecutionContext executionContext) throws Exception {
  5          final String message = "Execution was successful";
  6          MessagePane.showInfo(executionContext, message);
  7      }
  8  
  9  }
Die Schnittstelle IExceptionHandler

Die Schnittstelle IExceptionHandler hat die folgende Methode:

    void handleException(
        IExecutionContext executionContext, 
        final Exception exception) throws Exception;

Auch hier wird der IExecutionContext der auslösenden Action übergeben. Zudem bekommt man die aufgetretene Exception übergeben. Wenn man diese nicht selbst behandeln kann, kann man sie erneut werfen. Sie wird dann als nächstes vom Exception Handler der Action behandelt, falls ein solcher existiert.

Die Schnittstelle IEnabledChecker

Die Schnittstelle IEnabledChecker liefert die folgenden Methoden:

    IEnabledState getEnabledState();
    
    void addChangeListener(IChangeListener listener);

    void removeChangeListener(IChangeListener listener);

Die Methode getEnabledState() liefert die Information, ob die Aktion ausführbar ist. Immer wenn sich der EnabledState ändert, müssen die registrierten Listener darüber informiert werden.

Die Schnittstelle IEnabledState hat die folgenden Methoden:

    boolean isEnabled();

    String getReason();

Das bedeutet, es wird nicht nur die Information geliefert, ob eine Aktion ausführbar ist, sondern auch der Grund warum nicht. Die Methode getReason() sollte einen internationalisierten String zurückliefern, welche dem Nutzer Auskunft darüber gibt, warum die Aktion nicht ausführbar ist. Beispiele sind:

  • Speichern - Es gibt keine Änderungen

  • Speichern - Es existiert bereits ein Datensatz mit gleicher Artikelnummer

  • Undo - Es gibt keine Änderungen

  • Nachricht versenden - Die Nachricht hat keinen Betreff

  • Löschen - Fehlendes Recht

Es ist jedoch auch erlaubt null oder einen Leerstring zurück zu liefern.

Die Default Implementierung der Command Action tauscht, wenn der EnabledState disabled und der reason nicht leer ist, das Tooltip des gebundenen MenuItem, Button oder ToolbarButton gegen den reason Text aus.

Dies hat sich in großen und komplexen Enterprise Anwendungen als äußerst nützlich herausgestellt und wurde von Kunden mehrfach gelobt. So wurde sogar die Aussage getätigt, das man dieses Feature in vielen anderen Applikationen vermisse, seit dem man mit dieser Applikation arbeiten würde.

Wenn man bei Google den Text why is that button greyed out in eingibt, liefert einem die Autovervollständigung unzählige Fortführungen dieses Satzes, woraus sich vermuten lässt, dass sich Nutzer diese Frage häufig zu stellen scheinen. Da der Entwickler den Grund für das Ausgrauen in der Regel kennt, wäre es doch auch hilfreich, diesen an den Nutzer weiter zu geben, um die Usability zu erhöhen.

Die Klasse EnabledState liefert folgende statische Methode zur Erzeugung eines disabled State:

    public static EnabledState disabled(final String reason) {...}

Sowie die folgenden Konstanten:

    public static final EnabledState ENABLED = new EnabledState();

    public static final EnabledState DISABLED = new EnabledState(false, null);

Das folgende Beispiel demonstriert die Implementierung eines IEnabledChecker:

  1  public final class ModifiedEnabledChecker extends AbstractEnabledChecker {
  2  
  3      private final IInputComponent<?> inputComponent;
  4  
  5      private ModifiedEnabledChecker(final IInputComponent<?> inputComponent) {
  6          this.inputComponent = inputComponent;
  7  
  8          inputComponent.addInputListener(new IInputListener() {
  9              @Override
 10              public void inputChanged() {
 11                  fireEnabledStateChanged();
 12              }
 13          });
 14      }
 15  
 16      @Override
 17      public IEnabledState getEnabledState() {
 18          if (!inputComponent.hasModifications()) {
 19              return EnabledState.disabled("There is no modification");
 20          }
 21          else {
 22              return EnabledState.ENABLED;
 23          }
 24      }
 25  
 26  }

Dieser erlaubt die Ausführung nur, wenn die referenzierte Input Component Modifikationen hat. Für die Implementierung wird von der abstrakten Klasse AbstractEnabledChecker abgeleitet. Dadurch spart man sich die Implementierung Methoden addChangeListener() und removeChangeListener(). Um Änderungen anzuzeigen muss nur die Methode fireEnabledStateChanged() aufgerufen werden (Zeile 11).

Mit Hilfe der Klasse EnabledChecker könnte man das gleiche wie oben auch so erreichen:

  1      final EnabledChecker enabledChecker = new EnabledChecker();
  2  
  3      inputComponent.addInputListener(new IInputListener() {
  4          @Override
  5          public void inputChanged() {
  6              if (!inputComponent.hasModifications()) {
  7                  enabledChecker.setDisabled("There is no modification");
  8              }
  9              else {
 10                  enabledChecker.setEnabled();
 11              }
 12          }
 13      });

Je nach Anwendungsfall kann die eine oder andere Variante besser geeignet sein.


Siehe auch PDF Version dieses Dokuments, Jowidgets API Spezifikation