I am creating a JavaFX desktop app on which I am simulating some work load. I want the app to have a progress indicator that updates dynamically (with time passing at the moment) to show how the load process is progressing. This is my application class:
public class App extends Application {
#Override
public void init() throws InterruptedException{
//Simulation of time consuming code.
for(int i = 0; i<=10; i++) {
notifyPreloader(new Preloader.ProgressNotification(i/10));
System.out.println("Progress is being set by the app to: " + (i/10));
Thread.sleep(500);
}
}
#Override
public void start(Stage primaryStage) {
Parent root;
try {
root = FXMLLoader.load(getClass().getResource("/gui/fxml/App.fxml"));
Scene scene = new Scene(root, 600, 400);
scene.getStylesheets().add("/gui/style/app.css");
primaryStage.setScene(scene);
primaryStage.setTitle("Hello World!");
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
This is my preloader class:
public class AppPreloader extends Preloader {
private Stage preloaderStage;
private Parent root;
private Scene scene;
private ProgressIndicator progress_indicator;
#Override
public void start(Stage primaryStage) throws Exception {
this.preloaderStage = primaryStage;
this.preloaderStage.setScene(this.scene);
this.preloaderStage.show();
this.progress_indicator = (ProgressIndicator) scene.lookup("#progressIndicator");
}
#Override
public void init() throws Exception {
root = FXMLLoader.load(getClass().getResource("/gui/fxml/AppPreloader.fxml"));
Platform.runLater(new Runnable() {
#Override
public void run() {
scene = new Scene(root, 600, 400);
scene.getStylesheets().add("/gui/style/appPreloader.css");
}
});
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
if(pn instanceof ProgressNotification){
progress_indicator.setProgress(pn.getProgress());
System.out.println("Progress is being set by the handle method to: " + pn.getProgress());
}
}
#Override
public void handleStateChangeNotification(StateChangeNotification evt) {
if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
preloaderStage.hide();
}
}
}
Whit the print sentences I've been able to identify two problems: First, the
handleProgressNotification method is being called twice, once to be set to 0 and other to be set to 1, before the loop of the init method of the App class starts. Who is making the call? How can I avoid it?
The second problem is that the print sentence inside the init method of the app class is always printing 0.0. How can that be possible? Is it a matter of concurrency?
In addition I need to say that I've checked both of this questions (progressbar in preloader does not update and javafx preloader not updating progress) and didn't find a solution for my problem.
Thanks a lot for your time.
First, you're not seeing the progress values you expect because you are using integer arithmetic: i and 10 are both integers, so i/10 is 0 for 0 <= i < 10 and 1 when i=10.
Second, the handleProgressNotification and handleStateChangeNotification methods are part of the lifecycle of the application that are related to loading the resources. These are really leftovers from the days when JavaFX still supported web deployments and are probably of limited use now.
To receive notifications from the application, you need to override the handleApplicationNotification(...) method instead. Here is a corrected version of the two classes (also modified to be stand-alone so they can be copied and run: please provide these kinds of examples in your questions) that works:
package application;
import javafx.application.Application;
import javafx.application.Preloader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void init() throws InterruptedException{
//Simulation of time consuming code.
for(int i = 0; i<=10; i++) {
notifyPreloader(new Preloader.ProgressNotification(i/10.0));
System.out.println("Progress is being set by the app to: " + (i/10.0));
Thread.sleep(500);
}
notifyPreloader(new Preloader.StateChangeNotification(Preloader.StateChangeNotification.Type.BEFORE_START));
}
#Override
public void start(Stage primaryStage) {
Parent root;
root = new StackPane(new Label("Hello World"));
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("Hello World!");
primaryStage.show();
}
}
package application;
import javafx.application.Preloader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class AppPreloader extends Preloader {
private Stage preloaderStage;
private Parent root;
private Scene scene;
private ProgressIndicator progress_indicator;
#Override
public void start(Stage primaryStage) throws Exception {
progress_indicator = new ProgressIndicator();
root = new StackPane(progress_indicator);
scene = new Scene(root, 600, 400);
this.preloaderStage = primaryStage;
this.preloaderStage.setScene(this.scene);
this.preloaderStage.show();
}
#Override
public void handleApplicationNotification(PreloaderNotification pn) {
if (pn instanceof ProgressNotification) {
//expect application to send us progress notifications
//with progress ranging from 0 to 1.0
double v = ((ProgressNotification) pn).getProgress();
progress_indicator.setProgress(v);
} else if (pn instanceof StateChangeNotification) {
StateChangeNotification scn = (StateChangeNotification) pn ;
if (scn.getType() == StateChangeNotification.Type.BEFORE_START) {
preloaderStage.hide();
}
}
}
}
Related
How can I make a custom Event that triggers on Stage.setScene()?
In my code, the button switches the Scenes and that works fine. However, I would like to extend the Stage to have an additional Event that is triggered when a button or possibly any other Element triggers a setScene.
Example:
package sample;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
Group g1 = new Group();
Button b1 = new Button("2");
g1.getChildren().setAll(b1);
Scene scene1 = new Scene(g1, 50, 50);
Group g2 = new Group();
Button b2 = new Button("1");
g2.getChildren().setAll(b2);
Scene scene2 = new Scene(g2, 50, 50);
stage.setScene(scene1);
stage.setTitle("JavaFX Application Life Cycle");
b1.setOnAction(actionEvent -> {
System.out.println("1");
stage.setScene(scene2);
});
b2.setOnAction(actionEvent -> {
System.out.println("2");
stage.setScene(scene1);
});
stage.show();
}
}
You can add a ChangeListener<Scene> to your Stage like this:
stage.sceneProperty().addListener((observable, oldScene, newScene) -> {
System.out.println("New scene: " + newScene);
System.out.println("Old scene: " + oldScene);
});
I believe using a listener, as shown in the answer by #M.S., is probably the best and simplest way to react to scene changes. However, you ask about how to make a "custom event" that you can fire when the scene changes; by "event" I assume you mean a subclass of javafx.event.Event. So while I recommend sticking with a simple listener, here's an example of a custom event.
First, you need a custom event class:
import javafx.event.Event;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.stage.Window;
public class SceneChangedEvent extends Event {
public static final EventType<SceneChangedEvent> SCENE_CHANGED =
new EventType<>(Event.ANY, "SCENE_CHANGED");
public static final EventType<SceneChangedEvent> ANY = SCENE_CHANGED;
private transient Window window;
private transient Scene oldScene;
private transient Scene newScene;
public SceneChangedEvent(Window window, Scene oldScene, Scene newScene) {
super(window, window, SCENE_CHANGED);
this.window = window;
this.oldScene = oldScene;
this.newScene = newScene;
}
public Window getWindow() {
return window;
}
public Scene getOldScene() {
return oldScene;
}
public Scene getNewScene() {
return newScene;
}
}
I'm not sure what information you want to carry with the event so I just added the source Window as well as the old and new Scenes. If you're wondering about the ANY = SCENE_CHANGED, I'm just following the pattern used by javafx.event.ActionEvent (which also only has a single event-type).
Then you simply need to fire the event when the scene changes. To implement this you're still going to need a change listener. As you mention wanting to extend Stage here's an example of that:
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class CustomStage extends Stage {
private final ObjectProperty<EventHandler<? super SceneChangedEvent>> onSceneChanged =
new SimpleObjectProperty<>(this, "onSceneChanged") {
#Override
protected void invalidated() {
setEventHandler(SceneChangedEvent.SCENE_CHANGED, get());
}
};
public final void setOnSceneChanged(EventHandler<? super SceneChangedEvent> handler) {
onSceneChanged.set(handler);
}
public final EventHandler<? super SceneChangedEvent> getOnSceneChanged() {
return onSceneChanged.get();
}
public final ObjectProperty<EventHandler<? super SceneChangedEvent>> onSceneChangedProperty() {
return onSceneChanged;
}
public CustomStage() {
this(StageStyle.DECORATED);
}
public CustomStage(#NamedArg(value = "style", defaultValue = "DECORATED") StageStyle style) {
super(style);
sceneProperty().addListener((obs, ov, nv) -> fireEvent(new SceneChangedEvent(this, ov, nv)));
}
}
This would let you react to the scene changing using any of the following:
CustomStage stage = new CustomStage();
// addEventFilter/addEventHandler
stage.addEventFilter(SceneChangedEvent.SCENE_CHANGED, e -> { ... });
stage.addEventHandler(SceneChangedEvent.SCENE_CHANGED, e -> { ... });
// setOnSceneChanged
stage.setOnSceneChanged(e -> { ... });
Keep in mind that the event will only target the CustomStage instance. In other words, only event handlers added to the CustomStage instance will be notified of the event. And as you can see, this is much more complicated than simply adding a change listener to the scene property of the Stage.
I have problem with JavaFX. I created two scenes and switch button.
When I click that button I'm changing scene. But earlier i set fullscreen on true and after I pressed the button, windows taskbar shows for a moment. Is there any way to change scenes without having this taskbar visible?
There is the code:
----Main class----
import java.io.IOException;
public class Main {
public static void main(String[] args) throws InterruptedException, IOException {
DesktopApplication.launch(DesktopApplication.class);
}
}
----DesktopApplication class----
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class DesktopApplication extends Application implements Runnable {
Scene firstScene;
Scene secondScene;
Scene scene;
public static Stage primaryStagePublic;
public DesktopApplication() {
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Title");
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.initStyle(StageStyle.UNDECORATED);
int width = (int) Screen.getPrimary().getBounds().getWidth();
int height = (int) Screen.getPrimary().getBounds().getHeight();
HBox mainLayout = new HBox();
mainLayout.getChildren().add(new Text("hello!"));
MyLayout myLayout = new MyLayout(this);
firstScene = new Scene(myLayout,width,height);
secondScene = new Scene(mainLayout, width, height);
scene = firstScene;
primaryStage.setScene(scene);
primaryStage.setFullScreen(true);
primaryStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
primaryStage.show();
primaryStagePublic = primaryStage;
}
#Override
public void run() {
Thread thread = new Thread() {
public void run() {
launch(DesktopApplication.class);
}
};
thread.start();
while (true) {
}
}
public void swapScenes(Stage primaryStage){
primaryStage.setScene(secondScene);
primaryStage.setFullScreen(true);
primaryStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
}
}
----MyLayout class----
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class MyLayout extends HBox{
private DesktopApplication desktopApplication;
public MyLayout(DesktopApplication desktopApplication) {
this.desktopApplication = desktopApplication;
init();
}
private void init(){
this.setStyle("-fx-background-color: #f8ff7d;");
Label text = new Label("testing");
Button button = new Button("Button");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
desktopApplication.swapScenes(DesktopApplication.primaryStagePublic);
}
});
this.getChildren().addAll(text, button);
}
}
I had a similar issue and solved it like #James_D suggested: Do not replace the scene as a whole, but only the root element:
public void swapScenes(Parent newContent){
stage.getScene().setRoot(newContent);
}
This requires changing the rest of the initialisation code a bit:
public class DesktopApplication extends Application implements Runnable {
Parent myLayout;
Parent mainLayout;
Scene scene;
public static Stage stage; // if possible make this private and non static
public DesktopApplication() {
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Title");
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.initStyle(StageStyle.UNDECORATED);
int width = (int) Screen.getPrimary().getBounds().getWidth();
int height = (int) Screen.getPrimary().getBounds().getHeight();
mainLayout = new HBox();
mainLayout.getChildren().add(new Text("hello!"));
myLayout = new MyLayout(this);
scene = new Scene(myLayout,width,height);
primaryStage.setScene(scene);
primaryStage.setFullScreen(true);
primaryStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
primaryStage.show();
primaryStagePublic = primaryStage;
}
...
I personally solved it (After a few months of looking) by, instead of doing primaryStage.setFullScreen(true), which glitches or something, doing primaryStage.setMaximized(true) along with primaryStage.setWidth(var1) and primaryStage.setHeight(var2). My hypothesis on why setFullScreen deosn't work is a bug in full screen exclusive mode. Or, there just isn't enough permissions or something and it bugs out.
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 can add a ChangeListener to a Scene and call it on the scene.widthProperty() and
scene.heightProperty(), but this doesn't apply when the window is maximized via the Maximize button.
I can't find any onResize property of the window when it is accessed like scene.getWindow()
Here's what I have to resize columns in a table based off resizing the window.
How can I make that resizeColumns listener be added to when the whole window is Maximized (which doesn't qualify as a scene.widthProperty() or scene.heightProperty()
ChangeListener<Object> resizeColumns = new ChangeListener<Object>(){
#Override
public void changed(ObservableValue arg0, Object arg1, Object arg2) {
new Thread() {
// runnable for that thread
public void run() {
Platform.runLater(new Runnable(){
public void run() {
// what will be ran in gui thread
Double width =primaryStage.getWidth();
DraftController controller = (DraftController)loader.getController();
TableView<Player> teamTable =controller.getTeamTable();
centerColumns(width, controller, teamTable);
TableView<Player> top10Table = controller.getTop10Table();
AnchorPane anchor = controller.getAnchorPane();
centerColumns(anchor.getWidth()+anchor.getWidth()*.04,controller,top10Table);
}
private void centerColumns(Double width, DraftController controller, TableView<Player> teamTable) {
ObservableList<TableColumn<Player, ?>> columnList = teamTable.getColumns();
for (int i=0 ; i<columnList.size(); i++){
columnList.get(i).setPrefWidth((width-17)/teamTable.getColumns().size());
}
}
});
}
}.start();
}
};
scene.widthProperty().addListener(resizeColumns);
scene.heightProperty().addListener(resizeColumns);
guage: lang-java -->
My little example works well on maximizing the stage:
package org.example;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ResizeScene extends Application {
#Override
public void start(final Stage primaryStage) throws Exception {
final StackPane stack = new StackPane();
final Text resolution = new Text();
stack.getChildren().add(resolution);
final Scene scene = new Scene(stack);
primaryStage.setScene(scene);
final InvalidationListener resizeListener = new InvalidationListener() {
#Override
public void invalidated(final Observable observable) {
final double width = scene.getWidth();
final double height = scene.getHeight();
resolution.setText(width + " x " + height);
}
};
scene.widthProperty().addListener(resizeListener);
scene.heightProperty().addListener(resizeListener);
// Initial Size
primaryStage.setWidth(800);
primaryStage.setHeight(600);
primaryStage.show();
}
public static void main(final String[] args) {
launch(args);
}
}
I want to display 5 randomly positioned and colored circles. It was easy part. Now I want to adjust this code to be an animation. This application should generate random circles endlessly but the condition is that it should keep only last five circles on the screen. This is where I got stuck. JavaFx provides ListChangeListener. I think it is what I should use. But how?
The following is my unfinished code:
import java.util.Random;
import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class RandomColorTest extends Application {
int radius = 20;
int sceneWidth = 300;
int sceneHeight = 300;
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, sceneWidth,sceneHeight));
for (int i = root.getChildren().size(); i < 5; i++) {
root.getChildren().add(createCircle());
// the following should convey the idea:
// if the collection holds 5 elements then keep least recently generated element for 1 second and then delete it
// add one new element
// if the collection holds 5 elements then keep least recently generated element for 1 second and then delete it
// add one new element
// and so on
root.getChildren().addListener(new ListChangeListener<E>() {
#Override
public void onChanged(
javafx.collections.ListChangeListener.Change<? extends E> arg0) {
// TODO Auto-generated method stub
}
});
}
}
// Create randomly positioned and colored circle
private Circle createCircle() {
final Circle circle = new Circle();
circle.setRadius(radius);
Random r = new Random();
int rCol1 = r.nextInt(256);
int rCol2 = r.nextInt(256);
int rCol3 = r.nextInt(256);
int rX = radius+r.nextInt(sceneWidth);
if (rX>sceneWidth-radius) {
rX=rX-2*radius;
}
int rY = radius+r.nextInt(sceneHeight);
if (rY>sceneHeight-radius) {
rY=rY-2*radius;
}
circle.setLayoutX(rX);
circle.setLayoutY(rY);
circle.setStroke(Color.BLACK);
circle.setFill(Color.rgb(rCol1,rCol2,rCol3));
System.out.println(rCol1+"-"+rCol2+"-"+rCol3+"-"+rX+"-"+rY);
return circle;
}
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
After having managed to make ListChangeListener compile without errors it doesn't still work the way expected. Changes made to for loop:
for (int i = root.getChildren().size();;i++) {
final ObservableList<Node> ol = root.getChildren();
// the following should convey the idea:
// if the collection holds 5 elements then keep least recently generated element for 1 second and then delete it
// add one new element
// if the collection holds 5 elements then keep least recently generated element for 1 second and then delete it
// add one new element
// and so on
ol.add(createCircle());
ol.addListener( new ListChangeListener<Node>(){
#Override
public void onChanged(
javafx.collections.ListChangeListener.Change<? extends Node> arg0) {
// TODO Auto-generated method stub
System.out.println("one new element added, size:"+ol.size());
if (ol.size()==5) {
ol.remove(0);
}
}
});
}
For loop is defined to loop infinitely (probably not the right way to solve this problem also) and I can see from console that circles are removed and added during the program run. Alas, I can't see GUI anymore.
A similar question was also asked on the on the Oracle forums last year.
Here is sample solution using Timeline, which I prefer to a solution relying on worker threading. Though both can get the job done, I find using the JavaFX animation APIs more elegant and less error prone.
import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Random;
public class FiveAutoCircleExample extends Application {
private static final Random r = new Random();
public static final int SCENE_SIZE = 800;
public static void main(String[] args) throws Exception { launch(args); }
public void start(final Stage stage) throws Exception {
final Group circles = new Group();
final Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(.5),
new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
while (circles.getChildren().size() >= 5) circles.getChildren().remove(0);
int radius = 10 * r.nextInt(20);
circles.getChildren().add(
new Circle(
r.nextInt(SCENE_SIZE - radius * 2) + radius, r.nextInt(SCENE_SIZE - radius * 2) + radius,
radius,
new Color(r.nextDouble(), r.nextDouble(), r.nextDouble(), r.nextDouble())
)
);
}
})
);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
// display the scene.
stage.setScene(new Scene(circles, SCENE_SIZE, SCENE_SIZE, Color.CORNSILK));
stage.show();
}
}
In your code, you have some mistakes:
the GUI is not shown because, the execution flow never reaches the primaryStage.show(); due to infinite loop in the init(primaryStage);.
new ListChangeListener is added again and again in a loop. However you should add it only once in normal situations.
You are manipulating the ol (ol.remove(0);) in its own listener which triggers the new change event recursively.
As a solution: periodic tasks, long-time background executions can be separated to a different thread.
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, sceneWidth, sceneHeight));
final ObservableList<Node> ol = root.getChildren();
new Thread(new Runnable() {
#Override public void run() {
while (true) {
try {
// Wait for 2 seconds.
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
Platform.runLater(new Runnable() {
#Override public void run() {
System.out.println("ol size:" + ol.size());
if (ol.size() == 5) {
ol.remove(0);
}
ol.add(createCircle());
}
});
}
}
}).start();
primaryStage.show();
}
I have only changed the content of start(Stage primaryStage). There is no need to add a listener. This solution is very quick but not elegant way. You must manage the thread yourself. For more elegant and modern approach refer to Worker Threading in JavaFX 2.0.
In addition, if you really want a real animation then see the example Colorful Circles Application.