Java-Aktionen richtig benutzen

Hier fasse ich zusammen, wie Swing-GUI-Programme auf Benutzereingaben reagieren können und wie nach meinem aktuellen Wissen am besten vorzugehen ist (es gibt mehrere Möglichkeiten).

Der bevorzugte Weg ist, den JComponents Aktionen zuzweisen und mitzuteilen, durch welche Tasten diese ausgelöst werden.

Eine Aktion ist Code, der beim Eintreten bestimmter Ereignisse ausgeführt wird. Die Basisklasse ist Action, in der Regel wird eine Klasse abgeleitet von AbstractAction. Der Code wird in actionPerformed() ausgeführt.

1. Aktion auslösen anhand Tastatureingabe

In früheren Java-Versionen (vor 1.3) war die Aktion ein KeyListener bei einer Component. Aktuelles Vorgehen:

  1. String-Schlüssel definieren, der beschreibt, was geschieht, z.B. „Kopieren in die Zwischenablage“ (der Schlüsselname wird dann vielleicht COPY_TO_CLIPBOARD heißen). Dieser Schlüssel ist nicht Attribut (Field) der Aktion: Die Aktionen sollen ausgetauscht werden können (z.B. spezielles Kopieren in die Zwischenablage über eine andere Aktion), ebenso die Auslöser (z.B. unter Macintosh-Rechnern die Command-Taste anstelle der Strg-Taste oder frei definierbar durch die Benutzer) oder die Aktion soll entfernt werden können (keine Reaktion mehr auf diese Tastatureingabe).
  2. Tastatureingabe, die Aktion auslösen soll, diesem Schlüssel zuweisen über die InputMap der JComponent
  3. Aktion diesem Schlüssel zuweisen über die ActionMap der JComponent

Anmerkung: Eine JComponent hat 3 InputMaps, die in folgender Reihenfolge so lange abgearbeitet werden, bis für die Tastatureingabe ein Eintrag gefunden wurde: 1. Eine, sofern die JComponent fokussiert ist, 2. eine, falls die JComponent Vorgänger (Elterncontainer) einer fokussierten ist und 3. eine, falls das Window der JComponent fokussiert ist. 2./3. werden benutzt, falls eine Aktion ausgelöst werden soll unabhängig von der gerade fokussierten Komponente.

Weitere Informationen: How to Use Key Bindings.

2. Aktion Buttons und Menüs zuweisen

Die gleichen Aktionen können z.B. JMenuItems und JButtons über deren Konstruktoren zugewiesen werden (z.B. aus der ActionMap über deren Schlüssel holen).

3. Wie wird die Aktion aktiviert und deaktiviert?

Ist die Aktion von AbstractAction abgeleitet, ruft man setEnabled() auf. JButtons und JMenuItems sind PropertyChangeListener bei der Aktion, werden vom Aktivieren/Deaktivieren benachrichtigt und sind anschließend (wieder) aktiv oder nicht (mehr aktiv; „schwarz“ oder „ausgegraut“).

4. Offene Probleme

4.1 Wie gelangt die Aktion an die zu verarbeitenden Daten?

Ich gehe davon aus, die Aktion soll nicht lediglich z.B. einen Dialog anzeigen, sondern ausgewählte Daten verarbeiten, z.B. eine ausgewählte Datei löschen.

Manchmal erhält man die Daten – z.B. visualisiert als Knoten eines JTrees – über die Quelle der Aktion (Eigenschaft source des ActionEvents in actionPerformed(), im speziellen Fall des JTree) . Bei Menüaufrufen nutzt das nichts, da die Aktionsquelle das JMenuItem ist. Gleiches trifft zu, falls die Aktion an eine Vorfahren-Komponente gebunden ist, die nicht fokussiert ist, Aktionsquelle ist dann die Vorfahren-Komponente, nicht die fokussierte.

Wird von einem GUI-Builder eine Aktion erzeugt, ist diese oft eine anonyme innere Klasse eines GUIs, die Zugriff hat auf alle Attribute (Fields) des GUIs. Das ist nicht wiederverwendbar.

Man könnte alle Daten in der Aktion als Attribute speichern, was ich weniger gut finde (die vielseitigen Gründe sind hier nicht beschrieben, der vermutlich wichtigste: Die Aktion kann nicht „universell“ eingesetzt werden, sondern ist fest gekoppelt an eine spezialisierte JComponent).

In den oben aufgeführten Fällen wird man wiederholt Code schreiben, der die Daten mehr oder weniger aufwändig ermittelt, z.B. alle ausgewählten Knoten eines Baums herausfindet und anhand dieser die Daten, beispielsweise Dateisystemordner oder in einer Datenbank gespeicherte Werte.

4.2 Wie werden die Bedingungen festgestellt, unter denen die Aktion aktiviert/deaktiviert wird?

Ist in einem Baum kein Knoten selektiert, soll die Entf-Taste nichts löschen und das Kontextmenü soll keinen oder einen deaktivierten „Löschen“-Menüpunkt anzeigen.

Es sollte nicht Aufgabe der Aktion sein, jedes mal zu prüfen, ob sie ausgeführt werden soll.

4.3 Lösungsansatz

Aktuell favorisiere ich den Ansatz der NetBeans-Platform (der in dieser Form selbst zu implementierende spezialisierte Aktionen erfordert): Es gibt Container mit Objekten, sogenannte Lookups. Beobachter melden sich bei den Lookups an und ändert sich der Inhalt der Lookups, werden die Beobachter benachrichtigt (nur falls Klassen betroffen sind, die den Beobachter interessieren). Es gibt einen („globalen“) Lookup, der den Inhalt der gerade ausgewählten spezialisierten JComponent enthält.

Die Aktion ist ein Beobachter des (globalen) Lookups: Ist „interessanter“ Inhalt im Lookup, aktiviert sie sich und sie deaktiviert sich, ist das beobachtete Lookup leer. Wird die Aktion ausgeführt, benutzt sie den Inhalt des Lookups.

Aktionen interessieren sich nur für bestimmte Objekte: Eine, die Dateien umbenennt, benötigt nur die File-Objekte der umzubenennenden Dateien. Wäre sie an einen JTree, einer JList oder anderen JComponents gebunden, müsste sie jedesmal mehr oder weniger umständlich die Dateien daraus ermitteln und wäre begrenzt auf die spezialisierte JComponent.

Das Lookup löst beide Probleme (4.1 und 4.2): Die Aktionen sind nur aktiv, falls sinnvoll und sie haben die Daten, mit denen sie arbeiten wollen.

„Außerhalb“ der NetBeans-Platform sind folgende Probleme zu lösen (wie, zeigt der Artikel nicht auf):

  • Ein „globales“ Lookup definieren, bei dem die Aktionen als Beobachter angemeldet werden
  • In dieses Lookup sind bei Selektion die selektierten Objekte einzufügen; das Lookup enthält nichts (nichts ausgewählt) oder alle ausgewählten Objekte (nicht die „Low-Level“-Objekte wie TreeNodes, sondern die Daten, mit denen ein Programm arbeiten will) der aktiven (fokussierten) JComponent

5. Temporäre Selektionen

Auch hierfür bietet dieser Artikel keine Lösung (und auch nicht die NetBeans-Platform, diese ändert einfach die Auswahl): Wird mit der rechten Maustaste geklickt, während der Mauszeiger über einem „Item“ einer JComponent ist, z.B. einem Tree- oder List-Item, das nicht ausgewählt ist, erwartet der Benutzer:

  • Dieses Item wird nicht ausgewählt (Ist das Item z.B. ein Verzeichnis, werden nicht dessen Dateien angezeigt)
  • Dieses Item wird hervorgehoben
  • Die aktuelle Auswahl geht nicht verloren
  • Ein Popupmenü erscheint an der Item-Position
  • Alle Aktionen des Menüs beziehen sich auf das Item
  • Wird das Menü geschlossen, wird die Hervorhebung zurückgenommen

Eine Lösung wäre ein globales Lookup für temporäre Auswahlen, das mit dem globalen Lookup für Auswahlen verbunden ist und temporär, solange das Popupmenü geöffnet ist, dessen Inhalt ersetzt.

Siehe hierzu folgenden neueren Artikel, dieser beschreibt die Benutzung eines Lookups und enthält zum Download ein Beispielsprojekt.

Stichwörter:

Zu diesem Artikel können keine Kommentare mehr geschrieben werden.