Zum Ende der Metadaten springen
Zum Anfang der Metadaten

Sie zeigen eine alte Version dieser Seite an. Zeigen Sie die aktuelle Version an.

Unterschiede anzeigen Seitenhistorie anzeigen

« Vorherige Version anzeigen Version 15 Nächste Version anzeigen »

  • Einleitung
    • Übersicht über darauf folgende Themen/ Konzepte
    • Wer wird adressiert mit diesem Artikel
  • Focus Traversal (basics)
    • Zusammenspiel der JVM Klassen beschreiben 
      • Traversal Klassen
      • Focusable setzen
        • Java mittel
        • a11y property
    • verweis auf externe Ressources/Tutorials und Demo (von Andre)
  • Konzept in jadice viewer
    • Übersicht
      • standard jadice API
      • weg über TreeTraversal
    • TreeTraversal
      • Beispiel für TreeTraversal
      • ( Identifier
        • action.properties
          • verweis auf jadice Doku (Action & Command Framework)
          • mapping (accessibleName - Accessibility Support)
        • zusammen Spiel mit Annotationen/Annotation Toolbar 
          • verweis auf Anno Profil
    • FokusTraversal
      • Integrationsmöglichkeiten
        • In Anwendung
        • Weitere Anpassungen durch FocusTraversalKey setzen
    • Möglichkeiten und Grenzen des Konzepts
      • statische vs dynamische Komponenten 
      • Overlays
        • ThumbnailView
        • Anno Editoren
      • Externe Frames

Inhaltsverzeichnis

Einleitung

Dieser Artikel zum jadice viewer® wird ein Überblick darüber gegeben, wie man die Reihenfolge definiert, in der die Bedienelemente des Viewers fokussiert werden.
Der Artikel richtet sich an Integratoren, die aufgefordert sind, bestehende wie auch neue Anwendungen barrierefrei zu gestalten.
Nachfolgend werden Ihnen dafür erforderliche Konzepte und Code Beispiele erklärt.

Focus Traversal Policy

In Swing gibt es die Möglichkeit, mittels der Tastatur mit einzelnen Bedienelementen zu interagieren. Damit die Interaktion mit dem gewünschten Bedienelement erfolgen kann, muss dieses den Fokus haben. Dabei kann immer nur ein Bedienelement der GUI den Fokus erhalten. 
Eine Focus Traversal Policy bestimmt die Reihenfolge, in welcher die Bedienelemente den Focus erhalten. Jede Swing-Komponente besitzt eine Default Focus Traversal Policy. Je nach Lokation der Swing Komponente in der Swing-Komponenten-Hierarchie ergibt sich daraus die Fokus Reihenfolge der Anwendung. Dieses Standardverhalten kann durch das Implementieren einer eigenen FocusTraversalPolicy verändert werden.

Bei einer FocusTraversalPolicy werden nur Bedienelemente berücksichtigt, die einen Fokus erhalten können. Bedienelemente müssen somit zunächst fokussierbar gemacht werden.
Swing sieht für Komponenten die .setFocusable(true) Methode vor.
jadice® bietet mittels des Action & Command Framework die Möglichkeit, alle Menubar-Komponenten auf einmal fokussierbar zu machen. Dazu muss in der menucomponents.properties, folgender Boolean gesetzt werden:

enable.focussability.for.nonfocussable.components=true

Nachfolgend eine kleine Beispielanwendung für eine eigene FocusTraversalPolicy (siehe im Abschnitt „Beispiele“).
Weitere Informationen zum Thema FocusTraversalPolicy finden siehe hier: https://docs.oracle.com/javase/tutorial/uiswing/misc/focus.html#customFocusTraversal

Focus Traversal Policy in jadice®

Es gibt grundsätzlich zwei Vorgehensweisen, über die der Zugriff auf die Swing-Komponenten erfolgen kann. 

  • Zum einen kann die Referenz für die Komponenten vorgehalten werden, wie es auch in der „Beispielimplementierung für eine FocusTraversalPolicy“ der Fall ist. Hierbei muss die Sichtbarkeit Komponenten gegeben sein.
  • Zum anderen gibt es die Möglichkeit über das Traversieren der Swing Komponenten Hierarchie mittels Identifier auf die Komponenten zuzugreifen.

In den nachfolgenden Abschnitten wird die zweite Vorgehensweise näher erläutert.

Identifizierung der Swing-Komponenten

Ist der Zugriff auf Swing-Komponenten aufgrund ihrer Sichtbarkeit nicht möglich, machen wir uns eine Eigenschaft der jadice Implementation zunutze. Wir verwenden Informationen, die uns über den AccessibleContext der jeweiligen Komponente bereitgestellt werden. Im Besonderen nutzen wir die über die jadice Implementation sichergestellte Eindeutigkeit des AccessibleName (In Sonderfällen kann der Accessible Context auch integrationsseitig überschrieben werden).

AccessibleName aus den Action & Command Framework Dateien

Für die nachfolgend beschriebene Identifizierung der Swing-Komponenten, kann der verwendete AccessibleName aus den Property Dateien des Action & Command Framework ausgelesen werden. In der actions.properties Datei wird der name einer Action spezifiziert. Der dort verwendete Name der Action wird über die jadice implementation zu einem AccessibleName überführt. Anhand Action Name bzw. des AccessibleName kann bei der Traversierung der Swing-Komponenten-Hierarchie die gesuchte Komponente identifiziert werden.  

Beispiel:  

Erstellung der Policy anhand der Action Names
    // Store a list of references to the swing components
	List<AtomicReference<Component>> result = new ArrayList<>();
    // Accessible names we want to look for in the hierarchy
	String[] strings = {
        "Dokument drucken", "Dokumentinfo", "Linie"
    };
	// Get the references from the traversal
    for (String s : strings) {
      result.add(walkComponents((Container) root, s));
    }
	// Pass the references as input for the policy
    FocusTraversalPolicy policy = new HierarchyFocusTraversalPolicy(result);


AccessibleName aus dem Annotation Profil

Im falle der AnnotationsToolbar, die dynamisch anhand des verwendeten annotation profile erstellt wird, kann der AccessibleName aus dem annoprofil ausgelesen werden.
Hierfür muss der Wert ... als AccessibleName verwendet werden.  

Beispiel: 


(Warnung)  Notiz an uns selbst, man muss sich noch das Kontext-Menü genauer anschauen, da dort dynamisch anhand des anno-profils das Menü erstellt wird. (Warnung) 

Swing Komponenten Hierarchie Traversal

In Swing sind die einzelnen Komponenten in einer Hierarchie, die einer Baumstruktur entspricht, enthalten. Wenn man beispielsweise angefangen beim Wurzelelement der Swing Hierarchie beginnt durch die Elemente zu traversieren, kann man mittels des zuvor erwähnten AccessibleNames alle Elemente in dieser Baumstruktur wiederfinden. Ein Beispiel, wie so etwas angegangen werden kann, wird im Abschnitt "Beispiele" gezeigt. Wir verwenden im Beispiel dabei AtomicReferences (siehe https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html), um später Zugriff auf das Originalobjekt zu erhalten.

Integrationsmöglichkeit der eigenen Focus Traversal Policy

Möglichkeiten und Grenzen

Zuvor wurde die Vorgehensweise beschrieben, bei der Anhand des AccessibleNames Komponenten, während der Traversierung der Swing-Komponenten-Hierarchie, aufgefunden und für eine FocusTraversalPolicy verwendet werden können. 


statische vs dynamische Komponenten

Overlays (ThumbnailView, Annotations Editoren)

Fehlende AccessibleNames

Wenn eine Swing/AWT Komponente keinen AccesibleContext oder AccessibleName vergeben hat, kann dies die Identifizierung eines entsprechenden Elements in dem Ansatz mit der Traversierung erschweren. In diesem Fall müssen wahlweise andere Eigenschaften einer Komponente vergeben werden, die diese in dem Hierarchiebaum eindeutig identifizierbar machen oder aber auf den Ansatz mit dem Halten von Referenzen auf die Objekte ohne Traversierung ausgewichen werden.

Externe Frames

Hierarchie Listener

Bei manchen Swing/AWT basierten Anwendungen kann es zu einer Änderung der Swing-Hierarchie kommen. Wird eine FocusTraversalPolicy verwendet muss diese auf derartige Änderungen eventuell reagieren müssen je nach Anwendungsfall. Diesem Effekt kann mit einem HierarchyListener (siehe https://docs.oracle.com/javase/8/docs/api/java/awt/event/HierarchyListener.html) begegnet werden.

Beispiele

Beispielimplementierung für eine FocusTraversalPolicy

Beispielimplementierung für eine FocusTraversalPolicy
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FocusTraversalPolicy;
import java.awt.GridLayout;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class AccesibleTraversal {
  public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
      JFrame frame = new JFrame();
      frame.setLayout(new GridLayout(0, 5));
		// Create 5 buttons in order 3-1-4-2-5
      List<Component> buttons = new ArrayList<>();
      JButton button3 = new JButton("Button 3");
      frame.add(button3);
      JButton button1 = new JButton("Button 1");
      frame.add(button1);
      JButton button4 = new JButton("Button 4");
      frame.add(button4);
      JButton button2 = new JButton("Button 2");
      frame.add(button2);
      JButton button5 = new JButton("Button 5");
      frame.add(button5);
		// Hold a list for the focus order of the buttons
      buttons.add(button1);
      buttons.add(button2);
      buttons.add(button3);
      buttons.add(button4);
      buttons.add(button5);
      frame.setFocusTraversalKeysEnabled(true);
		// Policy that uses the order of the list for switching focus
      frame.setFocusTraversalPolicy(new ButtonTraversalPolicy(buttons));
      frame.setSize(new Dimension(500, 500));
      frame.setVisible(true);
    });
  }

  static class ButtonTraversalPolicy extends FocusTraversalPolicy {

    List<Component> components;

    public ButtonTraversalPolicy(List<Component> components) {
      this.components = components;
    }

    @Override
    public Component getComponentAfter(Container aContainer, Component aComponent) {
      int index = components.indexOf(aComponent);
      if (index >= 4) {
        return components.get(0);
      } else {
        return components.get(index + 1);
      }
    }

    @Override
    public Component getComponentBefore(Container aContainer, Component aComponent) {
      int index = components.indexOf(aComponent);
      if (index <= 0) {
        return components.get(components.size() - 1);
      } else {
        return components.get(index - 1);
      }
    }

    @Override
    public Component getFirstComponent(Container aContainer) {
      return components.get(0);
    }

    @Override
    public Component getLastComponent(Container aContainer) {
      return components.get(components.size() - 1);
    }

    @Override
    public Component getDefaultComponent(Container aContainer) {
      return components.get(0);
    }
  }
}

Baumsuche durch Traversierung der Swing-Hierarchie

Baumsuche in der Swing-Hierarchie
public static AtomicReference<Component> walkComponents(Container container,
      String nameToLookFor) {
    Component[] components = container.getComponents();
		// iterate through child components of the current swing component
    for (int i = 0; i < components.length; i++) {
      AccessibleContext accessibleContext = components[i].getAccessibleContext();
      if (accessibleContext != null) {
        String accessibleName = accessibleContext.getAccessibleName();
        if (nameToLookFor.equals(accessibleName)) {
		  // when a component is found we can set it to be focusable, so it can be used in the policy later on
          components[i].setFocusable(true);
          return new AtomicReference<>(components[i]);
        }
      }
	  // search in the sub tree
      AtomicReference<Component> comp = walkComponents((Container) components[i], nameToLookFor);
      if (comp != null) {
		// when a component is found we can set it to be focusable, so it can be used in the policy later on
        comp.get().setFocusable(true);
        return comp;
      }
    }
    return null;
  }

Beispielimplementierung für eine FocusTraversalPolicy mittels Traversierung der Swing-Hierarchie

FocusTraversalPolicy mittels Traversierung der Hierarchie
public class HierarchyFocusTraversalPolicy extends FocusTraversalPolicy {
    List<AtomicReference<Component>> order;

    public HierarchyFocusTraversalPolicy(List<AtomicReference<Component>> order) {
      this.order = order;
    }

    @Override
    public Component getComponentAfter(Container aContainer, Component aComponent) {
      if (!order.isEmpty()) {
        int index = getIndex(aComponent);
        if (index == -1) {
          return getDefaultComponent(aContainer);
        }
        if (index + 1 < order.size()) {
          return order.get(index + 1).get();
        } else {
          return getFirstComponent(aContainer);
        }
      }
      return null;
    }

    @Override
    public Component getComponentBefore(Container aContainer, Component aComponent) {
      if (!order.isEmpty()) {
        int index = getIndex(aComponent);
        if (index == -1) {
          return getDefaultComponent(aContainer);
        }
        if (index - 1 >= 0) {
          return order.get(index - 1).get();
        } else {
          return getLastComponent(aContainer);
        }
      }
      return null;
    }

    @Override
    public Component getFirstComponent(Container aContainer) {
      if (!order.isEmpty()) {
        return order.get(0).get();
      }
      return null;
    }

    @Override
    public Component getLastComponent(Container aContainer) {
      if (!order.isEmpty()) {
        return order.get(order.size() - 1).get();
      }
      return null;
    }

    @Override
    public Component getDefaultComponent(Container aContainer) {
      return getFirstComponent(aContainer);
    }

    public int getIndex(Component component) {
      for (int index = 0; index < order.size(); index++) {
        if (component.equals(order.get(index).get())) {
          return index;
        }
      }
      return -1;
    }
  }



  • Keine Stichwörter