I want to have a focus listener as a static variable that is passed throw static method, and its function is to close a stage when focus on that stage is lost.
i have the code:
Main class
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Scene scene = new Scene(loader.load(), 565, 551);
primaryStage.setScene(scene);
Controller controller = loader.getController();
primaryStage.show();
controller.setStage1InitOwner();
}
public static void main(String[] args) {
launch(args);
}
}
Controller class
public class Controller implements Initializable {
@FXML
private AnchorPane anchorPane;
// stage1 suppose to be a small dialogs
private final Stage stage1 = new Stage();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
stage1.setScene(new Scene(new Pane(new Label("a Dialog")), 200, 200));
// giving the listener to stage1 from static method
stage1.focusedProperty().addListener(Prepare.getFocusListener());
stage1.initStyle(StageStyle.TRANSPARENT);
}
public void setStage1InitOwner() {
stage1.initOwner(anchorPane.getScene().getWindow());
stage1.show();
}
}
Prepare class (static methods and variables inside)
public class Prepare {
public static ChangeListener<Boolean> focusListener = new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean t1) {
if (!t1) ;
// get the stage from observableValue and close it when focus is lost.
}
};
public static ChangeListener<Boolean> getFocusListener()
{
return focusListener;
}
}
fxml file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="anchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="551.0" prefWidth="565.0" style="-fx-background-color: #fffe00;" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
</children>
</AnchorPane>
now i want to get the caller stage from the ObservableValue inside the ChangeListener. i tried to cast it to BooleanProperty and then use getBean()
if (!t1) {
BooleanProperty booleanProperty = (BooleanProperty) observableValue;
Stage stage = (Stage) booleanProperty.getBean();
stage.close();
}
, but i got the following error:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: class javafx.beans.property.ReadOnlyBooleanWrapper$ReadOnlyPropertyImpl cannot be cast to class javafx.beans.property.BooleanProperty (javafx.beans.property.ReadOnlyBooleanWrapper$ReadOnlyPropertyImpl and javafx.beans.property.BooleanProperty are in module javafx.base of loader 'app')
at sample.Prepare$1.changed(Prepare.java:13)
at sample.Prepare$1.changed(Prepare.java:9)
at javafx.base/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:360)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
at javafx.base/javafx.beans.property.ReadOnlyBooleanPropertyBase.fireValueChangedEvent(ReadOnlyBooleanPropertyBase.java:78)
at javafx.base/javafx.beans.property.ReadOnlyBooleanWrapper.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:103)
at javafx.base/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
at javafx.base/javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:145)
at javafx.graphics/javafx.stage.Window.setFocused(Window.java:678)
at javafx.graphics/javafx.stage.Window$1.setFocused(Window.java:150)
at javafx.graphics/com.sun.javafx.stage.WindowHelper.setFocused(WindowHelper.java:112)
at javafx.graphics/com.sun.javafx.stage.WindowPeerListener.changedFocused(WindowPeerListener.java:64)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:126)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:40)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.lambda$handleWindowEvent$4(GlassWindowEventHandler.java:178)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.handleWindowEvent(GlassWindowEventHandler.java:176)
at javafx.graphics/com.sun.glass.ui.Window.handleWindowEvent(Window.java:1336)
at javafx.graphics/com.sun.glass.ui.Window.notifyFocus(Window.java:1315)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
at java.base/java.lang.Thread.run(Thread.java:833)
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: class javafx.beans.property.ReadOnlyBooleanWrapper$ReadOnlyPropertyImpl cannot be cast to class javafx.beans.property.BooleanProperty (javafx.beans.property.ReadOnlyBooleanWrapper$ReadOnlyPropertyImpl and javafx.beans.property.BooleanProperty are in module javafx.base of loader 'app')
at sample.Prepare$1.changed(Prepare.java:13)
at sample.Prepare$1.changed(Prepare.java:9)
at javafx.base/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:360)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
at javafx.base/javafx.beans.property.ReadOnlyBooleanPropertyBase.fireValueChangedEvent(ReadOnlyBooleanPropertyBase.java:78)
at javafx.base/javafx.beans.property.ReadOnlyBooleanWrapper.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:103)
at javafx.base/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
at javafx.base/javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:145)
at javafx.graphics/javafx.stage.Window.setFocused(Window.java:678)
at javafx.graphics/javafx.stage.Window$1.setFocused(Window.java:150)
at javafx.graphics/com.sun.javafx.stage.WindowHelper.setFocused(WindowHelper.java:112)
at javafx.graphics/com.sun.javafx.stage.WindowPeerListener.changedFocused(WindowPeerListener.java:64)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:126)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:40)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.lambda$handleWindowEvent$4(GlassWindowEventHandler.java:178)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.handleWindowEvent(GlassWindowEventHandler.java:176)
at javafx.graphics/com.sun.glass.ui.Window.handleWindowEvent(Window.java:1336)
at javafx.graphics/com.sun.glass.ui.Window.notifyFocus(Window.java:1315)
at javafx.graphics/com.sun.glass.ui.win.WinWindow._close(Native Method)
at javafx.graphics/com.sun.glass.ui.Window.close(Window.java:352)
at javafx.graphics/com.sun.glass.ui.win.WinWindow.close(WinWindow.java:316)
at javafx.graphics/com.sun.javafx.tk.quantum.WindowStage.lambda$close$4(WindowStage.java:824)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithRenderLock(QuantumToolkit.java:442)
at javafx.graphics/com.sun.javafx.tk.quantum.WindowStage.close(WindowStage.java:817)
at javafx.graphics/javafx.stage.Window$12.invalidated(Window.java:1157)
at javafx.base/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:110)
at javafx.base/javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:145)
at javafx.graphics/javafx.stage.Window.setShowing(Window.java:1190)
at javafx.graphics/javafx.stage.Window.hide(Window.java:1215)
at javafx.graphics/com.sun.javafx.stage.WindowCloseRequestHandler.dispatchBubblingEvent(WindowCloseRequestHandler.java:45)
at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
at javafx.graphics/com.sun.javafx.stage.WindowPeerListener.closing(WindowPeerListener.java:93)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:147)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:40)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.lambda$handleWindowEvent$4(GlassWindowEventHandler.java:178)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassWindowEventHandler.handleWindowEvent(GlassWindowEventHandler.java:176)
at javafx.graphics/com.sun.glass.ui.Window.handleWindowEvent(Window.java:1336)
at javafx.graphics/com.sun.glass.ui.Window.notifyClose(Window.java:1241)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
at java.base/java.lang.Thread.run(Thread.java:833)
so what's the problem, and what is the best way to get the stage.
Big Thanks in advance for StackOverflow team.
The error tells you what's wrong:
java.lang.ClassCastException: class javafx.beans.property.ReadOnlyBooleanWrapper$ReadOnlyPropertyImpl cannot be cast to class javafx.beans.property.BooleanProperty (javafx.beans.property.ReadOnlyBooleanWrapper$ReadOnlyPropertyImpl and javafx.beans.property.BooleanProperty are in module javafx.base of loader 'app')
You're trying to cast a ReadOnlyBooleanProperty
instance to a BooleanProperty
, which obviously is not possible. But you don't actually need to deal with such "narrow" implementations. All you care about is getting the "bean", and that only requires the type to be a ReadOnlyProperty<?>
(which is the top of the property class hierarchy).
ChangeListener<Boolean> listener = (obs, wasFocused, isFocused) -> {
if (!isFocused) {
var property = (ReadOnlyProperty<?>) obs;
var stage = (Stage) property.getBean();
stage.close();
}
};
However, you shouldn't need to deal with casting at all. If your goal is to simply avoid code duplication, then just create a utility method that accepts a Stage
and adds a listener to its focused
property.
For example:
import javafx.beans.value.ChangeListener;
import javafx.stage.Stage;
public final class CloseOnFocusLost {
private static final Object KEY = new Object();
public static void installListener(Stage stage) {
@SuppressWarnings("unchecked")
var listener = (ChangeListener<Boolean>) stage.getProperties().get(KEY);
if (listener == null) {
listener = (obs, wasFocused, isFocused) -> {
if (!isFocused) {
stage.close();
}
};
stage.getProperties().put(KEY, listener);
stage.focusedProperty().addListener(listener):
}
}
public static void uninstallListener(Stage stage) {
@SuppressWarnings("unchecked")
var listener = (ChangeListener<Boolean>) stage.getProperties().remove(KEY);
if (listener != null) {
stage.focusedProperty().removeListener(listener):
}
}
// prevent instantiation of utility class
private CloseOnFocusLost() {}
}
The above has a few advantages over your code:
ClassCastException
because the listener could be added to a property which does not belong to a Stage
.Stage
instance.focused
property.If you don't care about the last point, and don't ever need to remove the listener, then you can simplify the code to the following:
import javafx.stage.Stage;
public final class CloseOnFocusLost {
public static void installListener(Stage stage) {
stage.focusedProperty().addListener((obs, wasFocused, isFocused) -> {
if (!isFocused) {
stage.close();
}
});
}
// prevent instantiation of utility class
private CloseOnFocusLost() {}
}
In either case, you'd add your close-on-focus-lost listener with:
CloseOnFocusLost.installListener(theStageInstance);