2013-10-28 8 views
6

Ich habe eine ScrollPane mit fokussierbaren Knoten.JavaFX: Scrollen vs Fokus Traversal mit den Pfeiltasten

Das aktuelle Standardverhalten ist:

  • Shift- + , , , bewegt sich der Fokus

  • , , , scrollt die Ansicht

ich es anders wollen um. Wie kann ich dies erreichen oder wo soll ich anfangen?


[EDIT] Nun, ist es eine andere fragile Ansatz.

Statt mit den Ereignissen rumzualbern, könnte man mit der KeyBinding s herumspielen.

scrollPane.skinProperty().addListener(new ChangeListener<Skin<?>>() { 
     @Override 
     public void changed(ObservableValue<? extends Skin<?>> observable, Skin<?> oldValue, Skin<?> newValue) { 
      ScrollPaneSkin scrollPaneSkin = (ScrollPaneSkin) scrollPane.getSkin(); 
      ScrollPaneBehavior scrollPaneBehavior = scrollPaneSkin.getBehavior(); 
      try { 
       Field keyBindingsField = BehaviorBase.class.getDeclaredField("keyBindings"); 
       keyBindingsField.setAccessible(true); 
       List<KeyBinding> keyBindings = (List<KeyBinding>) keyBindingsField.get(scrollPaneBehavior); 
       List<KeyBinding> newKeyBindings = new ArrayList<>(); 
       for (KeyBinding keyBinding : keyBindings) { 
        KeyCode code = keyBinding.getCode(); 
        newKeyBindings.add(code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP || code == KeyCode.DOWN ? keyBinding.shift() : keyBinding); 
       } 
       keyBindingsField.set(scrollPaneBehavior, newKeyBindings); 
      } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { 
       LOGGER.warn("private api changed.", e); 
      } 
     } 
    }); 

Ich denke, das könnte der Reiniger Art und Weise sein, wenn KeyBindings mehr nicht statisch war, modifizierbar und Öffentlichkeit.

Antwort

4

Verwenden Sie eine event filter, um die relevanten Schlüsselereignisse zu erfassen und sie den verschiedenen Schlüsselereignissen zuzuordnen, bevor die Ereignisse zu blasen beginnen.

Re-mapping Standardschlüssel ist eine heikle Sache, die:

  1. den Benutzer verwirren kann.
  2. Möglicherweise unerwartete Nebenwirkungen (TextFields funktionieren möglicherweise nicht mehr wie erwartet).

Also mit Vorsicht verwenden:

import javafx.application.*; 
import javafx.event.*; 
import javafx.scene.Scene; 
import javafx.scene.control.*; 
import javafx.scene.input.KeyEvent; 
import javafx.scene.layout.TilePane; 
import javafx.stage.Stage; 

import java.util.*; 

public class ScrollInterceptor extends Application { 

    @Override 
    public void start(Stage stage) { 
    ScrollPane scrollPane = new ScrollPane(
     createScrollableContent() 
    ); 

    Scene scene = new Scene(
     scrollPane, 
     300, 200 
    ); 

    remapArrowKeys(scrollPane); 

    stage.setScene(scene); 
    stage.show(); 

    hackToScrollToTopLeftCorner(scrollPane); 
    } 

    private void remapArrowKeys(ScrollPane scrollPane) { 
    List<KeyEvent> mappedEvents = new ArrayList<>(); 
    scrollPane.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() { 
     @Override 
     public void handle(KeyEvent event) { 
     if (mappedEvents.remove(event)) 
      return; 

     switch (event.getCode()) { 
      case UP: 
      case DOWN: 
      case LEFT: 
      case RIGHT: 
      KeyEvent newEvent = remap(event); 
      mappedEvents.add(newEvent); 
      event.consume(); 
      Event.fireEvent(event.getTarget(), newEvent); 
     } 
     } 

     private KeyEvent remap(KeyEvent event) { 
     KeyEvent newEvent = new KeyEvent(
      event.getEventType(), 
      event.getCharacter(), 
      event.getText(), 
      event.getCode(), 
      !event.isShiftDown(), 
      event.isControlDown(), 
      event.isAltDown(), 
      event.isMetaDown() 
     ); 

     return newEvent.copyFor(event.getSource(), event.getTarget()); 
     } 
    }); 
    } 

    private TilePane createScrollableContent() { 
    TilePane tiles = new TilePane(); 
    tiles.setPrefColumns(10); 
    tiles.setHgap(5); 
    tiles.setVgap(5); 
    for (int i = 0; i < 100; i++) { 
     Button button = new Button(i + ""); 
     button.setMaxWidth(Double.MAX_VALUE); 
     button.setMaxHeight(Double.MAX_VALUE); 
     tiles.getChildren().add(button); 
    } 
    return tiles; 
    } 

    private void hackToScrollToTopLeftCorner(final ScrollPane scrollPane) { 
    Platform.runLater(new Runnable() { 
     @Override 
     public void run() { 
     scrollPane.setHvalue(scrollPane.getHmin()); 
     scrollPane.setVvalue(0); 
     } 
    }); 
    } 

    public static void main(String[] args) { 
    launch(args); 
    } 
} 
+0

Das ist genau das, was ich suchte. Ein kurzer erster Versuch, den Code in meine Anwendung zu übernehmen, führte jedoch zu einem 'StackOverflowError'. Ich werde mir das am Abend ansehen. Vielen Dank! –

+1

Die copyFor-Anweisung ist ein Schlüssel, andernfalls wird das JavaFX-System das Ereignis intern kopieren, um die Quelle und das Ziel festzulegen, sodass Sie in einer Endlosschleife der Ereigniszuordnung landen und den Verschiebemodifikator ein- und ausschalten, was zu einem Überlauf führt. Mein Testsystem war OS X mit Java 8. – jewelsea

+0

Irgendwie werden in meiner Anwendung keine Ereignisse aus der 'mappedEvents' Liste entfernt. Ihr Beispiel funktioniert gut. Ich konnte nicht herausfinden, was den Unterschied ausmacht. –