public void close(){
KeyValue opacity = new KeyValue(canvas
.opacityProperty(), 0);
KeyFrame end = new KeyFrame(Duration.millis(500),
opacity);
Timeline t = new Timeline(end);
t.play();
t.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
stage.close();
}
});
}
I have such a method, that changes opacity before quitting from app. It works fine, but during these 0.5 seconds Im still able to interact with UI. The question is how to stop everything except this Timeline execution before quitting?
Depends on how you set up your project. You could add a pane over the entire scene. Well, that doesn't prevent anyone from using the keyboard. So an alternative would be to consume all events like this:
root.addEventFilter(Event.ANY, e -> {
e.consume();
});
Call
stage.getScene().getRoot().setDisable(true);
before you start the timeline
Related
I try to make an application that shows a button. When I click the button, the scene should show some text (add a label) for a few seconds, and then the text should disappear (removing the label from the scene). But in fact when I click the button, nothing happens.
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
StackPane pane = new StackPane();
Button btn = new Button();
Label lb = new Label("Start");
pane.getChildren().addAll(btn);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
btn.setOnAction(e->{
pane.getChildren().addAll(lb);
try {
Thread.sleep(300);
} catch (Exception ex) {
ex.printStackTrace();
}
pane.getChildren().removeAll(lb);
});
}
public static void main(String[] args) {
launch(args);
}
}
Use a PauseTransition:
btn.setOnAction(e->{
pane.getChildren().addAll(lb);
PauseTransition pause = new PauseTransition(Duration.seconds(0.3));
pause.setOnFinished(e -> pane.getChildren().removeAll(lb));
pause.play();
});
The reason your approach doesn't work, is that JavaFX effectively uses a single thread for rendering and handling user events. It will update the screen at a fixed interval (60 times per second in the current implementation), but for synchronization reasons has to wait for any pending events that are currently being handled to complete first. So you original code adds the label, pauses for 0.3 seconds, and then removes the label. The FX Application Thread is occupied for this whole process, so the FX framework never has an opportunity to redraw the scene while it is happening.
The bottom line here is that you should never block the FX application thread, by calling sleep() or by executing long-running operations.
Update in response to additional question in comment:
To disable all event handling, you can call setDisable(true) on the root node of the scene. So to prevent event handling while the label is shown:
btn.setOnAction(e->{
pane.getChildren().addAll(lb);
pane.setDisable(true);
PauseTransition pause = new PauseTransition(Duration.seconds(0.3));
pause.setOnFinished(e -> {
pane.getChildren().removeAll(lb));
pane.setDisable(false);
});
pause.play();
});
I am building an alarm and it consists of two parts
an animated button created in javafx class and the engine which is created normally
what I need is whenever user press the animated button that closes the button and fire up the engine then after the engine is closed there will be some time then animated button appears again and so on
so I used ::
notify_me.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
new engine();
Platform.exit();
}
});
and in order to repeat this process I used
Timer t = new Timer(0,new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
while(true){
javafx.launch(javafx.class);
//some extra code goes here including sleep for
//some time and check for engine window state
}
}
});
t.start();
but I am facing two problems:
some extra code isn`t implemented until platform is exited,
launch() cannot be called more than once
so how can I achieve that without using threads ?? thanks
You probably won't get around using Threads. I'd recommend not shutting down the fx application thread however. Just close all windows and show (some of) them again after the delay:
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Hide me 5 sec");
// prevent automatic exit of application when last window is closed
Platform.setImplicitExit(false);
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
// timer should be a daemon (-> not prevent jvm shutdown)
Timer timer = new Timer(true);
btn.setOnAction((ActionEvent event) -> {
timer.schedule(new TimerTask() {
#Override
public void run() {
// make window reappear (needs to happen on the application thread)
Platform.runLater(primaryStage::show);
}
}, 5000l);
// hide window
primaryStage.close();
});
// allow exiting the application by clicking the X
primaryStage.setOnCloseRequest(evt -> Platform.exit());
primaryStage.show();
}
Im trying to update a label every certain seconds, I tried using a normal Timer but since its in another thread it cannot make changes to the label, this is the Timer:
public void setTimer(Timer timer, int seconds, String userName, String content, VBox tabContent,ArrayList<Integer> countTweetsArray, Label statusLabel) {
TabContent tabContentObj = new TabContent();
timer.schedule(new TimerTask() {
#Override
public void run() {
setTweet(userName, content);
//tabContentObj.createStatusScreen(tabContent, countTweetsArray, remainingTweets);
System.out.println(content+" after "+seconds);
System.out.println("countTweetsArray: "+countTweetsArray.get(0));
statusLabel.setText(countTweetsArray.get(0).toString());
countTweetsArray.set(0, (countTweetsArray.get(0)+1));
tabContentObj.timersMap.put(userName, timer);
}
}, (seconds*1000));
}
I read that I can make periodic changes to a label using TimeLine but I cant understand how it works the keyvalues and the keyframes, Is there a way to just trigger a void method without any animation involved?
You can use the KeyFrame constructor that takes a Duration and an event handler:
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(seconds), e -> {
// code to execute here...
})
);
timeline.play();
Update: if you need a button to stop it, you can do that with
Button button = new Button("Stop");
button.setOnAction(e -> timeline.stop());
I used to have a simple, timed (more-or-less) infinite loop:
while (!canrun) {
do_stuff(); //simple calculation
update_gui(); //updates some labels
Thread.sleep(waittime);
}
Which, naturally freezes the JavaFX-Application until it is finished with all calculations (canrun is set to false).
I replaced it with a timeline:
event = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
do_stuff();
update_gui();
}
};
keyframe = new KeyFrame(Duration.millis(waittime), event);
timeline = new Timeline(Timeline.INDEFINITE, keyframe);
do_stuff() looks like this:
public void do_stuff() {
do_the_actual_stuff();
if (stuff_finished) timeline.stop();
}
(waittime is here a number between 1 and 1000 (ms)).
On Buttonclick I start it with timeline.play(), and in do_stuff() I stop it when the calculations are done with timeline.stop().
I also have a function to change the waittime (even when it runs):
public void changewaittime() {
if (!(timeline.getStatus() == Animation.Status.RUNNING)) {
keyframe = new KeyFrame(Duration.millis(waittime), event);
timeline = new Timeline(Timeline.INDEFINITE, keyframe);
} else {
timeline.stop();
keyframe = new KeyFrame(Duration.millis(waittime), event);
timeline = new Timeline(Timeline.INDEFINITE, keyframe);
timeline.play();
}
}
And now my problem is, the Timeline only runs once, and not continuously, it doesn't even enter do_stuff() again, only if I call timeline.play() again. Even directly calling timeline.setCycleCount(Timeline.INDEFINITE) doesn't help.
Anything I missed?
Edit: I was unable to find my mistake, so I rewrote the complete GUI and now it is working.
In the Timeline constructor you are setting the frame rate using a constant meant for cycle count.
Use http://docs.oracle.com/javafx/2/api/javafx/animation/Animation.html#setCycleCount(int).
Solved the problem by rewriting my complete GUI and Timeline. Don't know why it didn't work before, but now it is working.
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.