1

I'm looking for the most concise way to deal with focus in an application which renders a map in a canvas component. You can pan the map location using arrow keys or ASWD keys. So far, I've been giving the canvas focus at startup and handling key pressed events via canvas.setOnKeyPressed().

This works fine, but I've always known that a problem was on the horizon when other components enter the picture. Once you interact with another component, it gains focus, and you're unable to scroll around the canvas map. I can prevent this from happening with some components like Hyperlinks or Buttons (I don't need tab-navigation) with something like this for those components:

sidePanel.getChildren().forEach(node -> node.setFocusTraversable(false));

But, when we get to things like TextArea or TextField, those do need to hold focus while they're being edited. And I'll need some way to return focus back (or at least unfocus those components) without being an annoyance to the user. (I don't want to have to click the canvas for it to regain focus.)

The options I see for returning focus back to the canvas after the user is done with those fields seem to be:

  1. Add a key handler (ex. ESC or ENTER keypress) on EACH of these components which returns focus back to the canvas.

    • Maybe not so concise, and a bit of a pain... also feels a bit fragile; if I miss something and the canvas loses focus, it would fail - I need a 100% reliable solution.
  2. Extend each of these components and add similar code to return focus back to Canvas

    • Even nastier and requires using custom components in Scene Builder, which is not ideal.
  3. Add a global event handler on the Scene and transmit events to the controller which owns the canvas

    • I believe an event filter would accomplish this - but on the other hand if the user is simply using arrow keys to move around a TextArea, I wouldn't want the Canvas map to move!
    • To solve the above problem, possibly the global event handler could ignore ASWD and arrow keypresses if the focus is on certain types of components? Is this worth trying, or am I neglecting a problem this would create?

Are there any other simple options out there that I've missed - and what would you suggest as the best option here? I'd like an automatic solution that doesn't require remembering to add some workaround code every time a UI component is added.

Manius
  • 3,594
  • 3
  • 34
  • 44
  • 1
    While this question is well thought-out and explained, I still feel that it is too general to be well answered here as is (I could be wrong). What would help is if you put together an [mcve] and placed it in your question. Include in it the canvas and the specific controls you are using, you don't need to action events like panning, just log whether the event was received. Then somebody copy that particular example and just compile it without change, then see if they can come up with a good concrete solution that works specifically for that (and post their modified version as an answer). – jewelsea Oct 08 '21 at 06:57
  • I also think you are misunderstanding the problem a bit. It is primarily an event handling problem, not a problem with receiving focus or traversing focusable fields, e.g. leave the default focus handling alone IMO. Something along the lines of option 3 (scene level handler/filter/accelerator) is the only thing that actually makes sense IMO. One easy solution is to use a modifier key combination (e.g. CTRL+KEYCODE in a filter at the scene level) for performing global operations affecting the canvas. – jewelsea Oct 08 '21 at 07:08
  • 2
    Most of my experience with applications that have a pannable area, plus input controls, use a combination of your first option (refocus pannable area on ESC/ENTER) and the "click on pannable area to focus" option you said you don't want. You could add the ESC/ENTER handling at the scene level (though that might interfere with text input controls, not sure). And even if you don't want to solely rely on the click-to-focus option, I would still implement that feature as I believe that's an intuitive behavior. But I don't consider myself a UX expert. – Slaw Oct 08 '21 at 07:09
  • Also study [javafx input event consumption](https://stackoverflow.com/questions/41132015/why-by-default-button-mouse-event-handler-consume-an-event), the [event handling tutorial](https://docs.oracle.com/javase/8/javafx/events-tutorial/events.htm) and [accelerators](https://stackoverflow.com/questions/12710468/using-javafx-2-2-mnemonic-and-accelerators). – jewelsea Oct 08 '21 at 07:11
  • try a custom focusTraversal - that is a ParentTraversalEngine [configured with a custom Algorithm](https://stackoverflow.com/a/30098563/203657), it's still internal api (didn't make into public at a time when much was to do ;) but useable. – kleopatra Oct 08 '21 at 08:44
  • I may come back to this eventually with an example or even an "answer", but I've thought of a 4th option (which is more design-dependent) that I'm going to try for now. Basically disable focus for things like buttons or hyperlinks which are displayed alongside the canvas, and use popups/dialogs for interactions which require dedicated focus such as text inputs. At least now I'm more confident that there isn't an easy/obvious solution that I neglected. – Manius Oct 12 '21 at 05:42

0 Answers0