Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent of FocusEvent.getOppositeComponent in JavaFx

Tags:

java

focus

javafx

In my JavaFx application, I want to call a method when the main frame gains focus. However, I want to react only in the case where the focus was outside my application and came back (not when a dialog closes for example).

When the application was in Swing, I could use the method

FocusEvent.getOppositeComponent

(which corresponds to the element that lost focus), and if it was null I knew the focus was previously outside my application.

I have not found any equivalent in JavaFX.

I have tried looking at window events, by adding an event filter on my window:

primaryStage.addEventFilter(Event.ANY, e -> System.out.println("event " + e));

but it doesn't track focus events.

like image 215
Balderk Avatar asked May 16 '19 16:05

Balderk


3 Answers

There is no equivalent in JavaFX. Focus changes are handled as a boolean property for each window separately, so you can only tell if a window received or lost focus. If you register a listener to all windows in your application, you could tell if one of them lost focus when another gained it.

There is no "FocusEvent" in JavaFX, you can find all event types listed in Event.

You can request the feature here.

like image 117
user1803551 Avatar answered Sep 25 '22 01:09

user1803551


I finally found a semi-satisfactory way of handling the problem, using the order of the events in JavaFX, so I'm posting it as an answer in case it can help others.

When a window w1 closes, giving focus to a window w2, the event order is as follow:

  1. w1 receives event WINDOW_HIDING
  2. w2 focusProperty changes to true
  3. w1 receives event WINDOW_HIDDEN

So I wrote the following code to allow me to know whether the focus comes from an internal window:

public class MainStage {
    private Stage primaryStage;
    private AtomicBoolean triggerEventOnFocusGain = new AtomicBoolean(true);

    ...

    primaryStage.focusedProperty.addListener((prop, oldVal, newVal) -> {
        if(newVal.booleanValue() && triggerEventOnFocusGain.get()) {
            doStuff();
        }
    });
}

public class SomeDialog {
    private MainStage mainStage;
    private Window dialogWindow;

    ...

    dialogWindow.addEventHandler(WindowEvent.WINDOW_HIDING, event ->
        mainStage.setTriggerEventOnFocusGain(false));
    dialogWindow.addEventHandler(WindowEvent.WINDOW_HIDDEN, event ->
        mainStage.setTriggerEventOnFocusGain(true));
}

The only issue is that I have to do that for all internal windows/dialogs.

In my case I eventually decided that I could get away doing that for only a handful of dialogs, for which them triggering the event would be problematic, and ignore the others.

The other way of course would be to introduce a common abstract parent of all my view classes that does the above code.

like image 36
Balderk Avatar answered Sep 23 '22 01:09

Balderk


JavaFX hierarchy is based on: Stage -> Scene -> Nodes -> ... -> Nodes:

enter image description here

If you want to listen focus of Stage (window), you can add listener to Stage focused Property of Stage:

Stage stage = ...
stage.focusedProperty()
        .addListener((observable, oldValue, newValue) -> {
                    if (!stage.isFocused()) { 
                       //action
                    }
                }
        );

This doesn't solve the problem in the question. You can't tell here what component had the focus. oldValue and newValue are booleans, so your if is trivial

You can check that you all Stages lost focuses (implement custom ChangeListener):

class AllStageUnfocusedListener implements ChangeListener<Boolean>{
    //IdentitySet and Runnable use only as example
    private final Set<Stage> stageSet;
    private final Runnable runnable;

    public AllStageUnfocusedListener(Runnable runnable) {
        this.stageSet =  Collections.newSetFromMap(new IdentityHashMap<>());
        this.runnable =  runnable;
    }

    public ChangeListener<Boolean> add(Stage stage){
        stageSet.add(stage);
        return this;
    }

    @Override
    public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
        if(isAllStageLostFocus()){
            runnable.run();
        }
    }

    private boolean isAllStageLostFocus() {
        for (Stage stage : stageSet) {
            if (stage.isFocused()) {
                return false;
            }
        }
        return true;
    }
} 

and add Listener to Focused Property:

AllStageUnfocusedListener changeListener = new AllStageUnfocusedListener(() -> { /* action */ });
Stage stage = ...
stage.focusedProperty()
            .addListener(changeListener.add(stage))
like image 42
kozmo Avatar answered Sep 25 '22 01:09

kozmo