My goal here is to have some animation on a node (such as a fade transition) that serves as a temporary notice that something is happening. I want the animation completely gone, like it never happened when that something has ended.
The code snipped below is an example of the problem I'm having. In the current state, when the button is hit to stop the process the button just stays at it's current opacity. If the commented line is uncommented, the button no longer stays at it's current opacity but updates to look correct. My problem then is that when the button is hit again, the CSS opacity for the default stylesheet (Modena.css for JavaFX 8) is no longer taking effect.
Is there something I'm doing wrong, or is there a better way altogether?
package gui.control.custom;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Stage stage = new Stage();
HBox box = new HBox();
streamButton = new Button("Start");
streamButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (started) {
stopProcess();
} else {
startProcess();
}
}
});
box.getChildren().add(streamButton);
stage.setScene(new Scene(box));
stage.show();
}
FadeTransition ft;
Button streamButton;
boolean started = false;
private void startProcess() {
streamButton.setDisable(true);
new Thread() {
#Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
}
Platform.runLater(() -> {
started = true;
streamButton.setText("Stop");
streamButton.setDisable(false);
startButtonAnim();
});
}
}.start();
}
private void stopProcess() {
streamButton.setText("Start");
stopButtonAnim();
started = false;
}
private void startButtonAnim() {
ft = new FadeTransition(Duration.millis(500), streamButton);
ft.setFromValue(1.0);
ft.setToValue(0.3);
ft.setCycleCount(Animation.INDEFINITE);
ft.setAutoReverse(true);
ft.play();
}
private void stopButtonAnim() {
ft.stop();
//streamButton.setOpacity(1);
}
public static void main(String[] args) {
launch();
}
}
I think the best solution is to use jumpTo(Duration duration) right before you stop the Animation. Setting the duration to Duration.ZERO.
Circle circle2 = new Circle(250, 120, 80);
circle2.setFill(Color.RED);
circle2.setStroke(Color.BLACK);
FadeTransition fade = new FadeTransition();
fade.setDuration(Duration.millis(5000));
fade.setFromValue(10);
fade.setToValue(0.1);
fade.setCycleCount(1000);
fade.setAutoReverse(true);
fade.setNode(circle2);
fade.play();
Button btnStop = new Button("Stop");
btnStop.setOnAction((event) -> {
fade.jumpTo(Duration.ZERO);
fade.stop();
});
Another idea:
I have found this method in javadoc: getCurrentRate(),
which should give you negative result on reversing, so the code would look like this:
private void stopButtonAnim() {
while(ft.getCurrentRate>=0); //waiting till animation goes (skips if already reversing)
while(ft.getCurrentRate<=0); //and till reverse
ft.stop(); //then stop
streamButton.setOpacity(1); //make it 100% ;)
}
Maybe you have to add Thread.sleep(int) to while cycle
I would try this insetad of simply stop(); this line
setOnFinished(e->tryToStop());
And create this method as:
public void tryToStop(){
if(!started)
fm.stop();
}
stopProcess() method changes the started variable, so it will stop in this two cases:
if it is finished
AND
if it is reqested to stop
Not tested, just an idea
Related
I have a class tasks, which handles multiple tasks using a menu layout, class tasks check the application flow by setting up the stage, with the menu scene to list all individual task. I want to run some task from the available list using there own classes, something like this:
Tasks.java:
package tasks;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.control.Button;
public class Tasks extends Application
{
private Stage window;
private Scene menuScene;
private Task1 task1;
public Tasks()
{
this.window=null;
this.menuScene=null;
this.task1=null;
}
private void setMenu()
{
VBox menu=new VBox();
Button newTask1Button=new Button("New Task 1");
newTask1Button.setOnAction(clickEvent -> this.startNewTask1());
menu.getChildren().add(newTask1Button);
//More buttons
this.menuScene=new Scene(menu,400,600);
this.window.setScene(this.menuScene);
}
private void startNewTask1()
{
this.task1=new Task1(this.window);
this.launchTask1();
}
private void launchTask1()
{
if(this.task1!=null)
{
int task1State=1;
//while(task1State==1) //To re-run for pause state
//{
task1State=this.task1.runTask1();
System.out.println("Task1 is in state "+task1State); //In no way part of program, just for debugging. Always give state=-1
//If 1-Paused, then display pause Menu for task1, by calling this.task1.paused(); and then again based on user input re-run runTask1
//If 0-Exit, then change the scene back to menuScene and quit the function
//}
}
}
#Override
public void start(Stage primaryStage)
{
this.window=primaryStage;
this.window.setTitle("Tasks");
this.setMenu();
this.window.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
Task1.java:
package tasks;
import javafx.stage.Stage;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.Button;
class Task1
{
private Stage window;
private Scene task1Scene;
private boolean intialised;
private int state;
public Task1()
{
}
public Task1(Stage _window)
{
this.window=_window;
this.task1Scene=null; //Will be set later
this.intialised=false;
this.state=-1;
}
private Scene createScene()
{
//Creates some GUI to interact
//Buttons in End, to control exit
HBox menu=new HBox();
Button pauseButton=new Button("Pause");
pauseButton.setOnAction(clickEvent -> this.state=1);
menu.getChildren().add(pauseButton);
Button exitButton=new Button("Exit");
exitButton.setOnAction(clickEvent -> this.state=0);
menu.getChildren().add(exitButton);
Scene scene=new Scene(menu,400,600);
return scene;
}
private void setupControls()
{
//To assign event handlers to interact with GUI
}
public int runTask1()
{
if(!this.intialised)
this.task1Scene=this.createScene();
this.window.setScene(this.task1Scene);
this.setupControls();
//while(this.state==-1);
return this.state;
}
}
The problem with this I face is, function runTask1() is always instantly returning, even though operation assigned using event handlers for Task1 are still running and no event for exit has been generated.
I tried to solve this by setting an instance variable named state and setting it to -1, and putting a while loop till this state variable is not modified. But that completely stops the GUI.
I realised its reason later by Googling, but couldn't determine which way to solve this.
At places, it was suggested to use Threads (not sure how, I don't want multiple processes running in the program) and at places, it was also suggested to set another Event Handler (but, they were running the different process in the start() function (inherited from Application) itself and it was more of transferring the flow rather than returning backwards).
How should I code to keep running only runTask1() till it is not finished, and return to launchTask1() sequentially?
The infinite while loop in method runTask1() in class Task1 is freezing the JavaFX application thread. Just remove it.
Basically your Task1 class is another Scene so when you click on button newTask1Button in class Tasks you simply want to set a new Scene.
Here is class Task1 with the required change.
package tasks;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.Button;
public class Task1 {
private Stage window;
private Scene task1Scene;
private boolean intialised;
private int state;
public Task1() {
}
public Task1(Stage _window) {
this.window = _window;
this.task1Scene = null; // Will be set later
this.intialised = false;
this.state = -1;
}
private Scene createScene() {
// Creates some GUI to interact
// Buttons in End, to control exit
HBox menu = new HBox();
Button pauseButton = new Button("Pause");
pauseButton.setOnAction(clickEvent -> this.state = 1);
menu.getChildren().add(pauseButton);
Button exitButton = new Button("Exit");
exitButton.setOnAction(clickEvent -> this.state = 0);
menu.getChildren().add(exitButton);
Scene scene = new Scene(menu, 400, 600);
return scene;
}
private void setupControls() {
// To assign event handlers to interact with GUI
}
public int runTask1() {
if (!this.intialised)
this.task1Scene = this.createScene();
this.window.setScene(this.task1Scene);
this.setupControls();
// while (this.state == -1)
// ;
return this.state;
}
}
As you can see, I simply commented out the while loop. The JavaFX application thread contains a loop that waits for user actions to occur such as moving the mouse or typing a key on the keyboard. You don't have to handle that in your code.
EDIT
As a result of the typo in the code in your question, that you mentioned in your comment to my answer and that you corrected in your question in a subsequent edit, I am editing my answer.
The way a JavaFX application works is that it reacts to user actions. You want class Tasks to be notified when the "state", in class Task1, is changed and the "state" is changed when the user clicks on either pauseButton or exitButton in class Task1. According to the code you posted, you could callback to class Tasks from the event handler of pauseButton.
Class Task1.
(Note comment CHANGE HERE and extra parameter in constructor.)
package tasks;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.Button;
public class Task1 {
private Tasks tasks;
private Stage window;
private Scene task1Scene;
private boolean intialised;
private int state;
public Task1(Stage _window, Tasks tasks) {
this.tasks = tasks;
this.window = _window;
this.task1Scene = null; // Will be set later
this.intialised = false;
this.state = -1;
}
private Scene createScene() {
HBox menu = new HBox();
Button pauseButton = new Button("Pause");
pauseButton.setOnAction(clickEvent -> tasks.setState(this.state = 1)); // CHANGE HERE
menu.getChildren().add(pauseButton);
Button exitButton = new Button("Exit");
exitButton.setOnAction(clickEvent -> this.state = 0);
menu.getChildren().add(exitButton);
Scene scene = new Scene(menu, 400, 600);
return scene;
}
private void setupControls() {
// To assign event handlers to interact with GUI
}
public int runTask1() {
if (!this.intialised) {
this.task1Scene = this.createScene();
}
this.window.setScene(this.task1Scene);
this.setupControls();
return this.state;
}
}
Class Tasks
(Added method setState(int).)
package tasks;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.control.Button;
public class Tasks extends Application {
private Stage window;
private Scene menuScene;
private Task1 task1;
public Tasks() {
this.window = null;
this.menuScene = null;
this.task1 = null;
}
private void setMenu() {
VBox menu = new VBox();
Button newTask1Button = new Button("New Task 1");
newTask1Button.setOnAction(clickEvent -> this.startNewTask1());
menu.getChildren().add(newTask1Button);
this.menuScene = new Scene(menu, 400, 600);
this.window.setScene(this.menuScene);
}
private void startNewTask1() {
this.task1 = new Task1(this.window, this);
this.launchTask1();
}
private void launchTask1() {
if (this.task1 != null) {
this.task1.runTask1();
}
}
#Override
public void start(Stage primaryStage) {
this.window = primaryStage;
this.window.setTitle("Tasks");
this.setMenu();
this.window.show();
}
public static void main(String[] args) {
Application.launch(args);
}
public void setState(int task1State) {
System.out.println("Task1 is in state " + task1State); // In no way part of program,
// just for debugging.
}
}
I use javafx, I have a TextField and a Button, when the button is pressed, it saves what is written in the TextField in a String. What I want to create is a method to mark a pause, while waiting for the Button to get pressed.
I have a class named pause.java, where I tried to put a obj.wait(); and a notifyAll(); in the event where the button is pressed, but the window isn't accessible during this time, I can't press the button or enter anything in the TextField.
So what I found was to put the obj.wait(); in a task, then I don't know why but it directly breaks out of the wait.
Here is my pause.java
package net.jpajavafx;
import java.util.logging.*;
import javafx.concurrent.Task;
public class pause {
Logger logger = Logger.getLogger(pause.class.getName());
MainController obj = new MainController();
public void waitinput() {
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
synchronized (obj) {
try {
String write = "Waiting for input...";
logger.log(Level.INFO, write);
obj.wait();
logger.log(Level.INFO, "Done");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
};
new Thread(sleeper).start();
}
}
How do I have to modify it to make it wait, while still having access to the GUI?
Here's my code simplified for the problem:
AlbumManager.java, where my main is.
package net.jpajavafx;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.*;
import javafx.application.*;
import javafx.fxml.FXMLLoader;
public class AlbumManager extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
primaryStage.setTitle("Album Manager");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java:
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
notifyAll();
}
public void startprogram() {
int etat = 0;
int run = 1;
while (run == 1) {
textarea.setText("1: launch method");
pause.waitinput(); // here I want to wait for an input
etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
}
It's really not clear what you're trying to achieve here that needs a separate thread: all the separate thread seems to try to do is wait until the button is pressed, and then execute some code. That functionality is already provided by the event management system in JavaFX (and the same is true for any UI toolkit): just execute the code in the event handler.
(As an aside, your use of wait() is incorrect, and if you fix that, the thread will never wake up because you are not calling notifyAll() on the same object on which you are calling wait().)
You can achieve what you seem to be trying to do simply with
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
processInput();
}
public void processInput() {
int etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
You have to start another thread using a Runnable, so your UI thread does not get blocked while the save-operation completes.
You can do this by placing a listener on the button that will start the save-operation on a new thread when the button is clicked.
The code for adding a listener to a button that starts a new thread would look something like this:
//Creating the mouse event handler
EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
MainController controller = new MainController();
controller.start();
}
};
//Registering the event filter
button.addEventFilter(MouseEvent.MOUSE_CLICKED, eventHandler);
The code you posted doesn't really do anything. Your call to waitinput() only logs and calls wait(). wait() is not what you want, since this operation is intended for putting a thread on hold until it is notified, not for executing a task in a seperate thread. Remove the obj.wait(), and add a listener that calls your logging method when the button is clicked. Also, get rid of the while-loop. The EventHandler will take care of events in the background.
I'm trying to update a progress bar in Java FX. My first problem was that the window said "not responding" instead of actually updating. It just froze and then after the tasks were done, the progress bar became full. So I found out that I had to use multithreading and implemented it like this.
overallList.clear();
progressbar.setprogress(0);
for(Object obj : list) {
class ThreadProgress implements Runnable { // inner class
public void run() {
thisList = scrape(obj);
overallList.add(thisList);
progressbar.setProgress(progressbar.getProgress() + (double)1/size);
}
}
Thread current = new Thread(new ThreadProgress());
current.start();
}
textAreaConsole.setText("Total number of things:" + overallList.size());
But now the problem is the final line prints "Total number of things: 0" because the threads don't actually finish executing before the machine runs the final line. Then I found out multiple ways to fix this, specifically using join() or ExecutorService. I implemented join() like this.
overallList.clear();
progressbar.setprogress(0);
List<Thread> threads = new ArrayList<Thread>();
for(Object obj : list) {
class ThreadProgress implements Runnable { // inner class
public void run() {
thisList = scrape(obj);
overallList.add(thisList);
progressbar.setProgress(progressbar.getProgress() + (double)1/size);
}
}
Thread current = new Thread(new ThreadProgress());
current.start();
threads.add(current);
}
for(Thread thread : threads) thread.join(); // with a try-catch loop
textAreaConsole.setText("Total number of things:" + overallList.size());
But this brings me back to the original problem, the window says "not responding" again. Same thing happened with ExecutorService. I have no idea what to do now.
See the example application below. It provides a simple ProgressBar and a Label to demonstrate how to update the UI with the progress of a background Task.
The code is commented as well.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ProgressBarExample extends Application {
// Create our ProgressBar
private ProgressBar progressBar = new ProgressBar(0.0);
// Create a label to show current progress %
private Label lblProgress = new Label();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Button to start the background task
Button button = new Button("Start");
button.setOnAction(event -> startProcess());
// Add our controls to the scene
root.getChildren().addAll(
progressBar,
new HBox(5) {{
setAlignment(Pos.CENTER);
getChildren().addAll(
new Label("Current Step:"),
lblProgress
);
}},
button
);
// Here we will
// Show the Stage
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void startProcess() {
// Create a background Task
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
// Set the total number of steps in our process
int steps = 1000;
// Simulate a long running task
for (int i = 0; i < steps; i++) {
Thread.sleep(10); // Pause briefly
// Update our progress and message properties
updateProgress(i, steps);
updateMessage(String.valueOf(i));
}
return null;
}
};
// This method allows us to handle any Exceptions thrown by the task
task.setOnFailed(wse -> {
wse.getSource().getException().printStackTrace();
});
// If the task completed successfully, perform other updates here
task.setOnSucceeded(wse -> {
System.out.println("Done!");
});
// Before starting our task, we need to bind our UI values to the properties on the task
progressBar.progressProperty().bind(task.progressProperty());
lblProgress.textProperty().bind(task.messageProperty());
// Now, start the task on a background thread
new Thread(task).start();
}
}
Edit: Added the setOnFailed() and setOnSucceeded() methods.
I want to develop an application that uses controlsfx Notifications to show some notifications in system tray mode. In normal mode my application works well and notification can be shown successfully.but when I hide stage in system tray , NullPointerException occurs. I don't know how i can fix this problem.
import java.awt.AWTException;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionListener;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class TryIconNotification extends Application {
private boolean firstTime;
private TrayIcon trayIcon;
#Override
public void start(Stage stage) throws Exception {
firstTime = true;
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
createTrayIcon(stage);
firstTime = true;
Platform.setImplicitExit(false);
stage.setScene(scene);
stage.show();
}
public void createTrayIcon(final Stage stage) {
if (SystemTray.isSupported()) {
// get the SystemTray instance
SystemTray tray = SystemTray.getSystemTray();
// load an image
java.awt.Image image = null;
image = Toolkit.getDefaultToolkit().getImage("icons\\iconify.png");
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent t) {
hide(stage);
}
});
// create a action listener to listen for default action executed on the tray icon
final ActionListener closeListener = new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
stage.hide();
}
};
ActionListener showListener = new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.show();
}
});
}
};
// create a popup menu
PopupMenu popup = new PopupMenu();
MenuItem showItem = new MenuItem("Open app");
showItem.addActionListener(showListener);
popup.add(showItem);
MenuItem closeItem = new MenuItem("Exit");
closeItem.addActionListener(closeListener);
popup.add(closeItem);
/// ... add other items
// construct a TrayIcon
trayIcon = new TrayIcon(image, "Systray", popup);
// set the TrayIcon properties
trayIcon.addActionListener(showListener);
// ...
// add the tray image
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.err.println(e);
}
// ...
}
}
public void showProgramIsMinimizedMsg() {
//only in first time show the message
if (firstTime) {
trayIcon.displayMessage("System Tray",
"Iconified",
TrayIcon.MessageType.INFO);
firstTime = false;
}
}
private void hide(final Stage stage) {
Platform.runLater(new Runnable() {
#Override
public void run() {
if (SystemTray.isSupported()) {
stage.hide();
showProgramIsMinimizedMsg();
} else {
System.exit(0);
System.out.println("Not Support Sys Tray");
}
}
});
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
And this is my controller Class:
import java.net.URL;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import org.controlsfx.control.Notifications;
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
Stage stage = (Stage) label.getScene().getWindow();
stage.hide();
}
public void createNotification() {
Notifications.create()
.text("This is a Notification")
.title("Notifications")
.showInformation();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Platform.runLater(()->createNotification());
}
}, 5000, 10000);
}
}
I realize that this exception occurs when the stage go to hide mode and the notification component cannot find stage when notification needs to show in stage. After searching in internet I find two solution for this problem.
Solution 1:
Open the stage and show notification.
In this way we should check that if the stage was hidden, open it , and show notification.
To do this we must add this condition in CreateNotification Method:
Stage stage = (Stage) button.getScene().getWindow();
if (!stage.isShowing()){
stage.show();
}
Solution 2:
In this solution we create a dummy stage and set its opacity to zero and after that, hide the main stage. I find this solution at this link and put the code in
here:
public void createDummyStage() {
Stage dummyPopup = new Stage();
dummyPopup.initModality(Modality.NONE);
// set as utility so no iconification occurs
dummyPopup.initStyle(StageStyle.UTILITY);
// set opacity so the window cannot be seen
dummyPopup.setOpacity(0d);
// not necessary, but this will move the dummy stage off the screen
final Screen screen = Screen.getPrimary();
final Rectangle2D bounds = screen.getVisualBounds();
dummyPopup.setX(bounds.getMaxX());
dummyPopup.setY(bounds.getMaxY());
// create/add a transparent scene
final Group root = new Group();
dummyPopup.setScene(new Scene(root, 1d, 1d, Color.TRANSPARENT));
// show the dummy stage
dummyPopup.show();
}
As I mention bellow, We should call this method before hiding the main stage:
#FXML
public void handleSysTryAction(ActionEvent event) {
Stage stage = (Stage) button.getScene().getWindow();
createDummyStage();
stage.hide();
}
I implement this two solution and every things works well. If you have a better solution for this problem please put here
You can download the complete Netbeans project from my Dropbox
I could not figure out why hamid's first solution was not working for me, until I debugged the Notifications creation. I found out, that beside the need of the Window to be isShowing it has to be isFocused too!
My solution is to call something like this method before Notifications.show():
private void focusStage() {
final Stage stage = (Stage) button.getScene().getWindow();
if (!stage.isShowing()) {
stage.show();
}
if (!stage.isFocused()) {
stage.requestFocus();
}
}
I'm working with Javafx and threads simultaneously and I constanly run into this problem where I make a button and then when the button is clicked (using event handlers) I make a for loop that changes the button to 1,2,3,4,5 and then delays for a second in the middle of each. Like a count down!
But what happens is it delays for 5 seconds and changes the text of button to 5.
The problem is I want to see it change between 1 and 5 but all I see is 5 at the end of a 5 second delay. I would assume that it changing the button text but I don't see it. I might have to to do with the .show() method in the Javafx class.
public class HewoWorld extends Application implements EventHandler<ActionEvent>
{
Thread t = new Thread();
Button butt;
boolean buttWasClicked = false;
Circle circ1 = new Circle(40, 40, 30, Color.RED);
Circle circ2 = new Circle(100, 100, 30, Color.BLUE);
Group root;
Scene scene;
Stage disStage = new Stage();
int i = 1;
public static void main(String[] args)
{
launch(args);
}
public void start(Stage stage) throws Exception
{
disStage.setTitle("tests stuffs");
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
double windh = bounds.getHeight()/2+150;//sets height of screen
double windw = bounds.getWidth()/3;//sets width of screen
Pane layout = new Pane();
butt = new Button();
butt.setText("Hello world");
root = new Group(circ1, circ2, butt);
scene = new Scene(root, 800, 400);
disStage.setWidth(windw);
disStage.setHeight(windh);
butt.setLayoutX(200);
butt.setLayoutY(200);
butt.setOnAction(this);
disStage.setScene(scene);
disStage.show();
}
public void handle(ActionEvent event)
{
if (event.getSource() == butt && buttWasClicked == false)
{
try
{
butt.setText(i+"");
t.sleep(1000);
i++;
}
catch(Exception q)
{
}
circ1 = new Circle(40, 40, 30, Color.BLACK);
circ2 = new Circle(100, 100, 30, Color.RED);
}
}
}
Why your code doesn't work
The reason your code doesn't work is that you are blocking the FX Application Thread.
Like (almost?) all UI toolkits, JavaFX is a single-threaded UI toolkit. This means that all event handlers, and all the rendering of the UI, are performed on a single thread (called the FX Application Thread).
In your code, you have an event handler that takes more than a second to run, because it pauses for a second via a call to Thread.sleep(...). While that event handler is running, the UI cannot be redrawn (because a single thread cannot do two things at once). So while the value of the button's text has changed immediately, the new value won't actually be rendered on the screen until the handle(...) method has finished running. If you had a for loop in the handle method, nothing would be rendered until the entire loop (and anything else in the method) had completed.
How to fix it
The simplest way to do what you want in JavaFX is to use a Timeline to handle the pause. The Timeline manages the threading appropriately for you:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountingButton extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Count");
Timeline timeline = new Timeline();
for (int count = 0; count <= 5 ; count++) {
final String text = Integer.toString(count);
KeyFrame frame = new KeyFrame(Duration.seconds(count), event ->
button.setText(text));
timeline.getKeyFrames().add(frame);
}
button.setOnAction(e -> timeline.play());
primaryStage.setScene(new Scene(new StackPane(button), 120, 75));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In general, for changing the appearance of the user interface at specific time points, the JavaFX Animation API (see also the tutorial) can be useful, especially Timeline and PauseTransition.
A "lower-level" way to do this would be to create a Thread yourself and pause in that thread. This is much more advanced: you need to be careful to update the UI on the FX Application Thread, not on the thread you created. You can do this with a call to Platform.runLater(...):
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class CountingButton extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Start");
button.setOnAction(e -> {
Thread thread = new Thread(() -> {
for (int i = 0; i <= 5 ; i++) {
final String text = "Count: "+i ;
Platform.runLater(() -> button.setText(text));
try {
Thread.sleep(1000);
} catch (InterruptedException exc) {
exc.printStackTrace();
}
}
});
thread.start();
});
primaryStage.setScene(new Scene(new StackPane(button), 120, 75));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For more general information on threading in JavaFX, have a look at this post: Using threads to make database requests
What you have to do is to replace the thread use by the following method :
scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(
new Runnable(){
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
//Here your code to change the number by for example incrementig the value of the button
}
});
}
},
1000,
80,
TimeUnit.MILLISECONDS);
+1 if it helps :D
In this case you need a timer to run every second and increment a counter on every hit. To my knowledge, the best way to make a timer in javafx is to use a timeline. https://stackoverflow.com/a/9966213/4683264.
int i = 0;// class field
// ....
Timeline fiveSecondsWonder = new Timeline(new KeyFrame(Duration.seconds(1), event ->
button.setText(++i)));
fiveSecondsWonder.setCycleCount(5);// repeat five times
fiveSecondsWonder.play();