...
Integrationsmöglichkeit der eigenen Focus Traversal Policy
...
Nachfolgend wird beschrieben wie die erstellte Focus Traversal Policy Integriert werden kann. Es wird auch auf das verändern des Standard FocusTraversalKey eingegangen.
Setzen einer Focus Traversal Policy
Um eine FocusTraversalPolicy zu setzen muss man nur die Policy auf der gewünschten Komponente setzen. Werden in der selben Hierarchie mehrere Komponenten mit einer Policy besetzt, so wird werden bei einem FocusTraversal diese Policies zunächst ignoriert. Wenn man die Beachtung dieser Policies wünscht so kann man wie in dem gezeigten Beispiel es als PolicyProvider markieren. Dann wird bei einer Suche dessen Policy ebenfalls zur Anwendung kommen, falls diese Komponente zurückgegeben wird bei einer Suche (Next/First/Last Component).
Codeblock | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
// create a sample hierarchy with a frame that has a panel which has 2 buttons JFrame frame = new JFrame(); JPanel panel = new JPanel(); JButton comp1 = new JButton(); JButton comp2 = new JButton(); frame.add(panel); panel.add(comp1); panel.add(comp2); // set a FocusTraversalPolicy on the root of the hiearchy frame.setFocusTraversalPolicy(new ExampleTraversalPolicy()); // set a different Policy on the panel. By default it will not be used when the frame has the focus and it receives a focus change panel.setFocusTraversalPolicy(new ExamplePanelTraversalPolicy()); // by setting it as providerFocusTraversalPolicyProvider its policy will be taken into account when the panel is the component that should receive the focus next panel.setFocusTraversalPolicyProvider(true); |
...
Standardmäßig wird TAB
verwendet, um bei der Komponente einen Fokuswechsel zur nächsten zu bewirken. Dies kann man durch das Setzen eigener Keys überschreiben. Über ein flag kann dabei gesteuert werden, ob die Komponente die Events des gesetzten Key
Möglichkeiten und Grenzen
Zuvor wurde die Vorgehensweise beschrieben, bei der Keys normal verarbeiten soll oder nur einen Fokuswechsel bewirken soll.
Codeblock | ||||||
---|---|---|---|---|---|---|
| ||||||
// Request the preset focus traversal keys of the component
Set<AWTKeyStroke> focusTraversalKeys = component.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
// Create a set with CTRL+N as FocusTraversalKey and the preexisting keys of the component
AWTKeyStroke ks = AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK);
Set<AWTKeyStroke> set = new HashSet<>(focusTraversalKeys);
set.add(ks);
// set them as new FocusTraversalKeys
frame.setFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set);
// decides whether the key event should be propagated after the focus change action. We only want to trigger focus changes for the component in this example, so we set it to true
frame.setFocusTraversalKeysEnabled(true); |
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 Diese Vorgehensweise ist nicht inn allen Fällen anwendbar, weshalb nachfolgen Besonderheiten und Grenzen aufgezeigt werden.
Dynamische Komponenten
Bei der Konstruktion einer Focus Traversal Policy muss man bestimmten dynamischen Komponenten besondere Beachtung schenken. Mit dynamischen Komponenten sind hier Komponenten gemeint, die dynamisch anhand von äußeren Faktoren wie beispielsweise einem gesetztem Annotationsprofil erzeugt werden. Ebenfalls können bestimmte Eigenschaften von Komponenten wie beispielsweise visuelle Sichtbarkeit oder ob die Komponente deaktiviert ist eine Relevanz bei der Reihenfolge des Fokus haben. Diese müssen bei einer Policy je nach gewünschtem Verhalten gesondert beachtet werden.
Overlays
...
Zum Beispiel die Annotationseditoren, das GalleryNavigationTool und die RolloutSearch unterstützen die in diesem Artikel Konzepte für die Tastaturbedienbarkeit momentan nicht. Wenn Sie diese dennoch in Ihrer Integration mit dem hier beschriebenen Konzept Verwenden wollen, stehen wir Ihnen gerne beraten zurseite.
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 (AccessibleContext) 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
Bei einer komplexen Anwendung kommt es häufig dazu, dass ein sekundärer Dialog oder Frame zum Einsatz kommt. Da dieses Frame oder dieser Dialog nicht Teil der Hierarche des Hauptfensters ist, muss dieses ebenfalls eine eigene Policy mitbringen, die den Fokus steuert. Wichtig ist dabei auch den Übergang zwischen den Frames zu beachten. Soll beispielsweise dem neuen Fenster Focus gegeben werden muss man mit requestFocus()
und gegebenenfalls toFront()
im Voraus aufgerufen werden um das Fenster in den Vordergrund zu rücken. Ob ein Fenster den Fokus bekommt, kann jedoch je nach Betriebssystem anders gehandhabt werden und die Priorität des Fensters spielt ebenfalls eine Rolle.
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.
...
Codeblock | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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
Codeblock | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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
Codeblock | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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; } } |
...