On my application(Media Player), when application is in fullscreen mode, I am fading out the top bar and bottom bar. I also wants to fade out the cursor after few seconds delay. I tried FadeTransition, but it take only Node as parameter.
I also tried using thread.
Platform.runLater(new Runnable()
{
#Override
public void run()
{
try
{
Thread.sleep(2000);
scene.setCursor(Cursor.NONE);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
});
Here I just wanted to hide the cursor after two seconds delay. It works, but also hang my application for two seconds which I don't expect.
In which way I can fade out the cursor after two seconds delay?
Your code is making the UI hang because you are making a blocking call (Thread.sleep(...)) on the FX Application Thread. If you want to do it that way, you should block on a background thread and schedule the change of the cursor on the FX Application Thread when the pause is complete.
An easier way, though, is just to use a PauseTransition:
PauseTransition pause = new PauseTransition(Duration.seconds(2));
pause.setOnFinished(e -> scene.setCursor(Cursor.NONE));
pause.play();
You can use this as well to make the cursor reappear on when the user does something, and disappear again after 2 seconds:
scene.addEventHandler(MouseEvent.ANY, e -> {
scene.setCursor(Cursor.DEFAULT);
pause.playFromStart();
});
Related
Right now as soon as the user clicks the first button the second one appears instantly, with or without thread. Is there a better way to do this?
// Register an event filter for a single node and a specific event type
getBtn1().addEventFilter(MouseEvent.MOUSE_CLICKED,
event -> {
if (event.getSource().equals(getBtn1())) {
getBtn1().setGraphic(new ImageView(getCrossImage()));
event.consume();
}
if (event.isConsumed()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
getBtn2().setGraphic(new ImageView(getNoughtsImage()));
}
});
You must not block the JavaFX application thread. Otherwise layout/rendering is paused until the method call completes. Since event filters run on the application thread you need to rewrite the code and allow the handler to complete without delay.
It's possible to use a PauseTransition for this purpose:
// Register an event filter for a single node and a specific event type
getBtn1().addEventFilter(MouseEvent.MOUSE_CLICKED,
event -> {
if (event.getSource().equals(getBtn1())) {
getBtn1().setGraphic(new ImageView(getCrossImage()));
event.consume();
PauseTransition pause = new PauseTransition(Duration.seconds(0.1));
pause.setOnFinished(evt -> getBtn2().setGraphic(new ImageView(getNoughtsImage())));
pause.play();
}
});
I would understand why
scene.setCursor(Cursor.WAIT);
long task...
scene.setCursor(Cursor.DEFAULT);
needs new threads; it works with:
private void set_cursore_attesa(final Scene scene)
{
Runnable r=new Runnable() {
#Override
public void run() {
scene.setCursor(Cursor.WAIT);
}
};
Thread t=new Thread(r);
t.start();
}
private void set_cursore_normale(final Scene scene)
{
Runnable r=new Runnable() {
#Override
public void run() {
scene.setCursor(Cursor.DEFAULT);
}
};
Thread t=new Thread(r);
t.start();
}
in my function:
set_cursore_attesa(scene);
long task...
set_cursore_normale(scene);
why I can't use the same thread?
I:
set my cursor to WAIT (it goes in GUI queue)
do my long task... (it goes in GUI queue but I expected that cursor changing, that is up in queue, it is executed before this)
reset my cursor to DEFAULT (after my task has finished)
So, my long task doesn't go in MAIN queue? because, if it goes in main queue, I expected it's executed after my WAIT cursor that is inserted in queue first.
Why this behavior?
Without the threads, your code is being executed on the FX Application Thread. This is the thread that is (effectively) responsible for rendering the UI to the screen and for processing user input. If you execute a long-running task on this thread, then you prevent any of the normal functionality of the FX Application Thread from occurring until your long-running task is complete. In particular, if you do
scene.setCursor(Cursor.WAIT);
longRunningTask();
scene.setCursor(Cursor.DEFAULT);
then the settings take place in the order you specify, but the scene does not get rerendered until all lines of code are complete. Hence you never actually see any changes to the UI - including the change to the cursor - until after your code is complete. The next time the FX Application Thread has an opportunity to render the scene, the cursor is set to Cursor.DEFAULT, and you never see the wait cursor.
There are two basic rules for multithreading and JavaFX (and the same rules generally apply to most UI toolkits):
Any changes to the UI must be performed on the FX Application Thread
Long-running processes should not be performed on the FX Application Thread (as they make the UI unresponsive)
So your solution is not actually correct, because you violate both of those rules. You should
Set the cursor to WAIT on the FX Application Thread
Start your long running task on a background thread
When the task is complete, set the cursor back to DEFAULT, on the FX Application Thread.
You can do this using a Task:
scene.setCursor(Cursor.WAIT);
Task<Void> task = new Task<Void>() {
#Override
public Void call() {
// long running task here...
return null ;
}
};
task.setOnSucceeded(e -> scene.setCursor(Cursor.DEFAULT));
new Thread(task).start();
So I want to display a Loading Screen(simple Stackpane with an ProgressIndicator on top of a blur etc..) over my Desktop Application while another Screen is loading data from the database and drawing itself. Up until now I used a Task in a new Thread to "split" my loading animation running in the FX thread from the logic in the background, problem is this time logic and drawing objects on the screen in the bakckground are heavily intervowen and I cannot split them without stupenduous amount of work.
So I found out about AnimationTimer, which apparently is called on every frame and sends a pulse which causes an equivalent to a repaint() in Swing. And so I tried to use it thus:
public void showApplicationScreen(){
public void addAndShowTransporterStatus() {
AnimationTimer at = new AnimationTimer() {
int i;
#Override
public void handle(long now) {
i++;
LOG.info(i); //testing to see frames per second
}
};
at.start();
showLoadingIndicator(true);
loadDataFromDBandDrawObjects();
showLoadingIndicator(false);
at.stop();
}
Is there some kind of trick to it that I am missing? Or some other (simple) way?
I cant believe something so simple is so complicated to do. Gotta say I wish every Node had a repaintAtIntervall(double timespan) method that would suspend everything else the application is doing atm and repaint itself before continuing with the normal flow. Might not be pretty but it sure as hell would be useful.
You really shouldn't need an AnimationTimer for something like this. If you are doing the initial loading in a background thread, use a Task. Show a loading screen and hide it in the task's onSucceeded handler. You can create node instances in a background thread as long as they are not part of the scene graph, so while it's not a particularly good design, you can do something like:
Task<Parent> createMainScene = new Task<Parent>() {
#Override
public Parent call() {
Parent root = ... ;
// load data etc., create structure below root
// call updateMessage(...) to update a status message if needed
// call updateProgress(...) to update the progress if needed
// ...
return root ;
}
};
ProgressBar pBar = new ProgressBar();
pBar.progressProperty().bind(createMainScene.progressProperty());
Label statusLabel = new Label();
statusLabel.textProperty().bind(createMainScene.messageProperty());
VBox root = new VBox(5, statusLabel, pBar);
Stage loadingStage = new Stage(new Scene(root));
loadingStage.show();
createMainScene.setOnSucceeded(e -> {
primaryStage.setScene(new Scene(createMainScene.getValue()));
primaryStage.show();
loadingStage.hide();
});
new Thread(createMainScene).start();
A better (more properly-separated) design would be to have the task just load the application data and process it, and return an object encapsulating the data. Then in the onSucceeded handler you would create the UI from the data (which should not take a long time). However, it sounds like you cannot do that with the current code with which you're working.
Javafx Layer put HBox
I am Developing app in javafx And I have Situation where I am updating content of HBox . So how I can notify user to wait some time while it processing . So for that I want to put loading image on Hbox like layer while it update that layer is removed.
Just Like SwingWorker in swing after done we can update scenario is there any way to do it in javafx
you cannot do background processing on UI thread, it will result in freezing UI, so you have to use threads.
Task task = new Task() {
#Override
protected Object call() throws Exception {
hBox.setVisible(true);
// Thread.sleep(5000);
// Processing
return null;
}
};
task.setOnSucceeded(event -> {
hBox.setVisible(false);
});
new Thread(task).start();
Vaadin newbie: When a user presses a button, I like to disable it so he knows that he clicked it and there's some work going on in the background. When the (long) task is completed, I'd like to enable the button.
For this, I'm using 2 threads (background and work) but for some reason the button doesn't enabled at the end of the task.
In other words, once clicked it goes to enabled(false) and never coming back. Why? and how can I fix it?
button.addClickListener(new ClickListener()
{
public void buttonClick(ClickEvent event)
{
Thread background = new Thread(new Runnable(){
#Override
public void run()
{
Thread work = new Thread(new Runnable(){
#Override
public void run()
{
button.setEnabled(false);
try
{
Thread.sleep(2000); //long work here!
} catch (InterruptedException e)
{
e.printStackTrace();
}
button.setEnabled(true); //doesn't enable at the end of the long work!
}});
work.start();
try
{
work.join();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}});
background.start();
}
});
Maybe the best approach would be to use Button.setDisableOnClick(true) for the button and do the processing directly in the event handler without a background thread. This will show the standard loading indicator to the user until processing is done.
Otherwise you need to enable server push (#Push) and remember to use UI.access() in the background thread before updating the UI. See https://vaadin.com/book/-/page/advanced.push.html