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.
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.
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:
- w1 receives event WINDOW_HIDING
- w2 focusProperty changes to true
- 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.
JavaFX hierarchy is based on: Stage
-> Scene
-> Nodes
-> ...
-> Nodes
:
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
andnewValue
are booleans, so your if is trivial
You can check that you all Stage
s 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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With