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:
Enabled Checking prüft, ob die Aktion ausführbar ist.
Execution führt die eigentliche Aktion aus
Exception Handling behandelt Ausnahmen
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
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
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
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.