I have to write a method that will do something when the user closes a window. So far I managed to write this code but it does not work (i placed it in my initialize method in my controller) :
Scene scene = myTable.getScene();
Window window = null;
if (scene != null)
{
window = scene.getWindow();
System.out.println("scene is not null");
window.addEventHandler(WindowEvent.WINDOW_HIDDEN, new EventHandler<WindowEvent>
()
{
#Override
public void handle(WindowEvent w)
{
System.out.println("do somethong here");
};
});
Unfortunately Even my message "scene is not null does not get displayed. Does anyone have a better idea on how to do it?
If you want to do something when the user closes the window you should use the setOnCloseRequest() method like this :
window.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
//do something
}
});
Now if scene is null then this code won't be executed and nothing will happen, maybe a little System.out.println(scene); before the test would help you debug this issue.
Add a change listener to the scene property of the table, and only add your event handler when the scene is changed to a non-null value.
As recommended by Marc, calling setOnCloseRequest or setOnHidden, is probably a better way to configure your EventHandler.
Related
like the title implies i've got a problem with my application. The application is supposed to run in fullscreen mode (no intention for switching back to window mode), so i designed a footer-bar holding some images (with a Label, in a VBox) so the user could navigate or exit the program.
So after starting the application all Buttons work just fine with touch. Even the Exit-button in my footer-bar responded correctly by opening my custom Dialog. But here starts my Problem. The Dialog is shown by showAndWait()-Method call, but does not respond to Touch-Events. In contrary mouse-events are still processed (i still can use a mouse to click the Buttons in my Dialog and the Dialog is responding correctly).
I hope someone got an idea what i'm doing wrong.
MyDialog.java:
public static boolean showExitDialog(Window owner, ResourceBundle resources) {
LOGGER.info("Showing exit dialog...");
final Dialog<ButtonType> dialog = new Dialog<ButtonType>();
dialog.getDialogPane().getStylesheets().add(MyDialog.getInstace().getCssPath());
dialog.setContentText(resources.getString("label.exitdialog.text"));
dialog.setHeaderText(resources.getString("label.exitdialog.header"));
dialog.initOwner(owner);
dialog.initStyle(StageStyle.TRANSPARENT);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.getDialogPane().getButtonTypes().add(new ButtonType(resources.getString("btn.Exitdialog.exit"), ButtonData.OK_DONE););
dialog.getDialogPane().getButtonTypes().add(new ButtonType(resources.getString("btn.Exitdialog.cancel"), ButtonData.FINISH));
Optional<ButtonType> result = dialog.showAndWait();
LOGGER.debug("Result: {}", result.get());
if(result.isPresent() && result.get().getButtonData() == ButtonData.OK_DONE) {
LOGGER.info("Closing exit dialog returning true...");
return true;
} else {
LOGGER.info("Closing exit dialog returning false...");
return false;
}
}
In MainApp.java:
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
// close event handling logic.
// consume the event if you wish to cancel the close operation.
if(MyDialog.showExitDialog(primaryStage, rb)) {
event.consume();
System.exit(0);
}
};
...
primaryStage.setOnCloseRequest(confirmCloseEventHandler);
In FooterBar.java:
#FXML
private void exitProgramPressedTouch(TouchEvent event) {
event.consume();
controller.getWindow().fireEvent(new WindowEvent(controller.getWindow(), WindowEvent.WINDOW_CLOSE_REQUEST));
}
*Edit* Oh totally forgot: No Exception or anything else is thrown.
I don't know the reason for the described behavior - maybe a bug. However, you could try to listen for ActionEvent instead of TouchEvent. It handles both touch and mouse events:
#FXML
private void exitProgramPressedTouch(ActionEvent event) {
event.consume();
controller.getWindow().fireEvent(new WindowEvent(controller.getWindow(), WindowEvent.WINDOW_CLOSE_REQUEST));
}
Maybe you need also to change the attribute which binds the event listener (from onTouch to onAction) in your FXML file.
Finally, I think, you could avoid System.exit(0); if you consume the close event only when the cancel button has been clicked:
if(!MyDialog.showExitDialog(primaryStage)) {
event.consume();
}
Having a
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
//lot of method calling here then
getWindow().removeWindow(popup);
log.warn("Removed Popup");
}
I would expect to show a popup window and after some milisecundom (after the expensive method calls) it should hide itself. The log says :
2014-02-19 15:26:51 WARN xyzClass:82 - Added POPUP
2014-02-19 15:26:51 WARN xyzClass:135 - Removed Popup
But the truth is that there is no popup showing here.
If i only show it, and not remove it later (the popup will show)
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
//lot of method calling here then
log.warn("Removed Popup");
}
My main reason for this i want to achieve a glasspanel/loading screen functionality # Vaadin, and not had found better solution yet. Any solution/description why the popup not shown up i would appreciate
Just do not have time to render it. You add it and immediately remove.
Try this approach, for example:
private MyPopup popup;
public void buttonClick(ClickEvent event) {
Thread workThread = new Thread() {
#Override
public void run() {
// some initialization here
getWindow().removeWindow(popup);
}
};
workThread.start();
popup = new MyPopup();
getWindow().addWindow(popup);
}
Depending on Vaadin version you can make use of ICEPush plugin (Vaadin 6) or built-in feature called Server Push (Vaadin 7).
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
// start background thread with ICEPush or ServerPush
}
// Background thread in a separate class
// update UI accordingly when thread finished the job
getWindow().removeWindow(popup);
log.warn("Removed Popup");
Thanks to it you can move your time-consuming operations to another class thus decouple your business logic from the presentation layer. You can find examples of usage in the links above.
I use JavaFX 2.1 and I created GUI using FXML, in the controller of this GUI I added myTextField.requestFocus();.
But I always get the focus in the other control.
At the time of initialize() controls are not yet ready to handle focus.
You can try next trick:
#Override
public void initialize(URL url, ResourceBundle rb) {
Platform.runLater(new Runnable() {
#Override
public void run() {
tf.requestFocus();
}
});
}
For tricky complex applications (like Pavel_K has in the comments) you may want to repeat this routine several times and call method line next one:
private void requestFocusOrDieTrying(Node node) {
Platform.runLater(() -> {
if (!node.isFocused()) {
node.requestFocus();
requestFocusOrDieTrying(node);
}
});
}
Note this is the undocumented approach and it may be wise to add a limit for repetitions to avoid endless loop if something changed or broke in future Java releases. Better to lose focus than a whole app. :)
Example with the described threshold:
#Override
public void requestFocus() {
requestFocus( getNode(), 3 );
}
private void requestFocus( final Node node, final int max ) {
if( max > 0 ) {
runLater(
() -> {
if( !node.isFocused() ) {
node.requestFocus();
requestFocus( node, max - 1 );
}
}
);
}
}
The exact same answer as #Sergey Grinev. Make sure your version of java is up-to-date (JDK 1.8 or later).
Platform.runLater(()->myTextField.requestFocus());
If you requestFocus(); after initializing the scene, it will work!
Like this:
Stage stage = new Stage();
GridPane grid = new GridPane();
//... add buttons&stuff to pane
Scene scene = new Scene(grid, 800, 600);
TEXTFIELD.requestFocus();
stage.setScene(scene);
stage.show();
I hope this helps. :)
This can occur when the Scene property for the Node is not yet set.
Alas, the scene property can take a "long" time to be set.
The child node's scene property lags when a scene is first created, and also, when items are added to some parents, such as a TabPane (oddly some parents seem immune, I'm not sure why).
The correct strategy, which has always worked for me :
if (myNode.scene) == null {
// listen for the changes to the node's scene property,
// and request focus when it is set
} else {
myNode.requestFocus()
}
I have a handy Kotlin extension function which does this.
fun Node.requestFocusOnSceneAvailable() {
if (scene == null) {
val listener = object : ChangeListener<Scene> {
override fun changed(observable: ObservableValue<out Scene>?, oldValue: Scene?, newValue: Scene?) {
if (newValue != null) {
sceneProperty().removeListener(this)
requestFocus()
}
}
}
sceneProperty().addListener(listener)
} else {
requestFocus()
}
}
You can then call it from within you code like so :
myNode.requestFocusOnSceneAvailable()
Perhaps somebody would like to translate it to Java.
I ran into the same problem using JavaFX 11 and solved it in a similar way that nickthecoder proposed.
ChangeListener<Scene> sceneListener = new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
if (newValue != null) {
editInput.requestFocus();
editInput.sceneProperty().removeListener(this);
}
}
};
editInput.sceneProperty().addListener(sceneListener);
Basicly just add a listener to the sceneProperty of the node and in that listener request focus once the scene is set. I also wrote it in such a way that the listener will be removed after it is invoked.
I would rather using timer to enforce focus to text field. The process of checking whether or not the text field has focus, is done in a separate (background) thread. While the process of requesting focus is done in the GUI thread, with the help of Platform.runLater().
//I'd rather using timer to enforce focus
Timer checkIfTFIsFocusedTimer = new Timer();
TimerTask checkIfTFIsFocusedTask = new TimerTask() {
#Override
public void run() {
if (!textField.isFocused()) {
Platform.runLater(() -> {
textField.requestFocus();
});
} else {
checkIfTFIsFocusedTimer.cancel();
}
}
};
checkIfTFIsFocusedTimer
.scheduleAtFixedRate(checkIfTFIsFocusedTask,
0, 100);
The older answers account for the case of Platform.runLater not working, but this answer covers also the case of multiple requests on multiple nodes.
Problem is: the order in which the scene property becomes non-null for the nodes, and thence the order in which the added listeners get called, is not necessarily the same as the order in which the two listeners were added. And so this order:
requestFocusOnSceneAvailable(node1)
requestFocusOnSceneAvailable(node2)
might unexpectedly result in this order:
node2.requestFocus()
node1.requestFocus()
A solution requires having the listeners call requestFocus() only on the most recent node, which can be tracked with a static variable:
private static Node nodeToRequestFocusOnOnceSceneAvailable;
public static void requestFocusOnceSceneAvailable(Node node) {
// Remember this node as the latest node requested to receive focus.
nodeToRequestFocusOnOnceSceneAvailable = node;
// Schedule the focus request to happen whenever
// JavaFX finally adds the node to the scene.
Listeners.addAndFire(node.sceneProperty(), new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> observable, Scene oldScene, Scene newScene) {
if (newScene != null) {
if (node == nodeToRequestFocusOnOnceSceneAvailable) {
node.requestFocus();
// We no longer need to remember this node,
// since its focus has been requested.
nodeToRequestFocusOnOnceSceneAvailable = null;
}
// We no longer need the listener
// after it has run once.
observable.removeListener(this);
}
}
});
}
Note, this solution assumes there is only one scene.
I currently have some code that makes a button in the primaryStage that spawns a new stage. My goal is to have the button close the stage it's on using the setOnMouseClicked method right after launching the new one. Here is how it's currently setup:
#Override
public void start(Stage primaryStage) {
setPlayBtn();
}
private void setPlayBtn() {
play = new ImageView(new Image(BugWars.class.getResourceAsStream("images/play-btn.png")));
play.setFitHeight(50);
play.setFitWidth(50);
play.setX(375);
play.setY(375);
play.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
setGame(); // This creates the new stage.
primaryStage.close();
}
});
Unfortunately this doesn't work. Netbeans complains that it can't find the symbol. It thinks it's a variable. I'm sure it's something stupid, but any help referencing the primaryStage would be appreciated. Thanks guys!
So I've worked around the problem by simply making the PlayBtn instatiate inside of the start() method that(I believe) creates primaryStage and then making primaryStage final. I don't know why this works, but it does.
I have an SWT WizardDialog with a number of pages. When this dialog first opens I have to do a check for some conditions and if those conditions are met I need to show a popup over the freshly opened dialog.
So I have this code to listen for SWT.Show event. The event listener responds to SWT.Show to conduct its tests and show a message box:
final WizardDialog dialog = new WizardDialog(shell, wizard);
dialog.setTitle("New Wizard");
dialog.create();
dialog.getShell().addListener(SWT.Show, new Listener()
{
private boolean firstShowing = true;
#Override
public void handleEvent(Event event)
{
if (firstShowing && someConditionExists())
{
MessageBox messageBox = new MessageBox(dialog.getShell(), SWT.OK
| SWT.ICON_WARNING);
messageBox.setMessage("Test");
messageBox.open();
firstShowing = false;
}
}
});
dialog.open();
Except it's called too soon! The dialog is not visible when the handler is called. My message box appears before the dialog is visible and the dialog only appears when I dismiss the message box.
So clearly the SWT.Show is unreliable, at least on Windows where I'm running it. I've also tried putting this code into a ShellListener on the activation but that happens even before the SWT.Show example above.
So how do I reliably show a message box when the dialog is made visible?
Plan B is a dirty timer based hack where a timer is set to fire 200 ms into the future and hope that it triggers when the dialog is visible but obviously this could introduce it's own issues.
I'm using in similar situation (need that appStarted() is called after application window is visible) something like below.
public class App extends ApplicationWindow {
#Override
protected Control createContents(Composite parent) {
// ...
getShell().addShellListener(new ShellAdapter() {
#Override
public void shellActivated(ShellEvent shellevent) {
if (!started) {
Shell s = (Shell) shellevent.getSource();
s.setVisible(true);
appStarted();
started = true;
}
}
});
}
}
Maybe You can use the same like below:
final WizardDialog dialog = new WizardDialog(shell, wizard);
dialog.setTitle("New Wizard");
dialog.create();
dialog.getShell().addShellListener(new ShellAdapter() {
#Override
public void shellActivated(ShellEvent shellevent) {
if (firstShowing && someConditionExists()) {
Shell s = (Shell) shellevent.getSource();
s.setVisible(true);
MessageBox messageBox = new MessageBox(dialog.getShell(), SWT.OK | SWT.ICON_WARNING);
messageBox.setMessage("Test");
messageBox.open();
firstShowing = false;
}
}
});
dialog.open();
Instead of hooking the SWT.Show event, you may get more luck with hooking a PaintListener on to your dialog's Composite. (You'll probably want to unhook it during the first execution.)
What about overriding dialog.open() methodon your WizardDialog class? The first line of the overridden method would call super.open(), which would make it visible. Just put your custom code after that, in the .open() method.
The issue with the approach you're taking above appears to be that it responds to a Show event, which is simply notification that Show has been requested, not that the dialog is visible. The Show event could very well be designed to allow you to know when something is about to be shown, and take some action before that happens, as you've experienced.
I know that this is an old thread. But in case someone finds it useful, I found that overriding Dialog.create() rather than Dialog.open() worked for me.
it's called too soon!
I also run recently in the same trouble. The code was executed too early - my upload action (which I wanted to start automatically under some conditions) was started before the page was displayed.
This happens because the page can only be shown after the code in the SWT.SHOW listener or in the inherited setVisible() method is completed.
#Override
public void setVisible(boolean visible) {
if (visible) {
org.eclipse.ui.progress.UIJob("Auto start the upload") {
#Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (isAutoStartQcUploadSelected)
startUpload();
return Status.OK_STATUS;
}
};
uiJob.schedule();
}
super.setVisible(visible);
}
org.eclipse.ui.progress.UIJob as described FAQ_Can_I_make_a_job_run_in_the_UI_thread has solved the issue.
P.S.: Yes, I know that's an old question :-)
But it is the first one propesed by google and the hint with the UI Job was missing.
The code of marioosh can be further improved, by storing the ShellAdapter in a variable.
Remove the ShellAdapter when the listener is triggered for the first time.
The variable started is no longer needed.
The statement s.setVisible(true); is not necessary, because this event is just triggered when the shell gets visible.
public class App extends ApplicationWindow {
#Override
protected Control createContents(Composite parent) {
// ...
ShellAdapter shellActivatedAdapter = new ShellAdapter() {
#Override
public void shellActivated(ShellEvent shellevent) {
shellevent.getSource().removeShellListener(shellActivatedAdapter);
appStarted();
}
};
getShell().addShellListener(shellActivatedAdapter);
}
}