I'm designing a stopwatch using JavaFX. The code runs well. Except for enormous cumulative memory leaks over time. The leak increases whenever I increase the Timeline's framerate. I'm currently on Ubuntu 16.04 with 4gigs of RAM, and the leak is happening at a speed of 300MB/min at 30fps. That's 5MBps. I can understand that this may happen due to the repetitive drawing over the Scene, but why would it be cumulative? Shouldn't the JVM take care of this?
Main.java :
package UI;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("StopWatch");
primaryStage.setScene(new Scene(getPane(), 400, 400));
primaryStage.show();
}
private BorderPane getPane(){
BorderPane pane = new BorderPane();
ClockUI clockUI = new ClockUI();
clockUI.setMinSize(200,200);
pane.setCenter(clockUI);
ButtonBar buttonBar = new ButtonBar();
Button startButton = new Button("Start");
startButton.setOnAction(e->clockUI.startClock());
Button pauseButton = new Button("Stop");
pauseButton.setOnAction(e->clockUI.stopClock());
Button resetButton = new Button("Reset");
resetButton.setOnAction(e->clockUI.resetClock());
buttonBar.getButtons().addAll(startButton, pauseButton, resetButton);
pane.setBottom(buttonBar);
return pane;
}
public static void main(String[] args) {
System.setProperty("prism.lcdtext","false");
launch(args);
}
}
ClockUI.java :
package UI;
import javafx.animation.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
/**
* Created by subhranil on 23/6/17.
*/
public class ClockUI extends StackPane {
private final Rotate hourRotate;
private final Rotate minuteRotate;
private final Rotate secondRotate;
private final Timeline hourTimeline;
private final Timeline minuteTimeline;
private final Timeline secondTimeline;
private final ParallelTransition clockTransition;
public ClockUI() {
super();
Line hourHand = getHand(80, Color.WHITE);
hourRotate = getRotate(hourHand);
hourTimeline = createRotateTimeline(Duration.hours(12), hourRotate);
Line minuteHand = getHand(100, Color.WHITE);
minuteRotate = getRotate(minuteHand);
minuteTimeline = createRotateTimeline(Duration.minutes(60), minuteRotate);
Line secondHand = getHand(90, Color.WHITE);
secondRotate = getRotate(secondHand);
secondTimeline = createRotateTimeline(Duration.seconds(60), secondRotate);
clockTransition = new ParallelTransition(hourTimeline, minuteTimeline, secondTimeline);
Circle back = new Circle(120);
back.centerXProperty().bind(widthProperty().divide(2));
back.centerYProperty().bind(heightProperty().divide(2));
back.setStyle("-fx-fill: #555555");
setStyle("-fx-background-color: #333333;");
getChildren().addAll(back, hourHand, minuteHand, secondHand);
}
private Timeline createRotateTimeline(Duration duration, Rotate rotate) {
Timeline timeline = new Timeline(30);
timeline.getKeyFrames().add(new KeyFrame(duration, new KeyValue(rotate.angleProperty(), 360)));
timeline.setCycleCount(Animation.INDEFINITE);
return timeline;
}
public void startClock() {
if (clockTransition.getStatus() != Animation.Status.RUNNING) {
clockTransition.play();
}
}
public void stopClock() {
if (clockTransition.getStatus() == Animation.Status.RUNNING) {
clockTransition.pause();
}
}
public void resetClock() {
stopClock();
clockTransition.stop();
}
private Rotate getRotate(Line line){
Rotate r = new Rotate(0);
r.pivotXProperty().bind(line.startXProperty());
r.pivotYProperty().bind(line.startYProperty());
line.getTransforms().add(r);
return r;
}
private Line getHand(int size, Paint color) {
Line hand = new Line();
hand.startXProperty().bind(widthProperty().divide(2));
hand.startYProperty().bind(heightProperty().divide(2));
hand.endXProperty().bind(widthProperty().divide(2));
hand.endYProperty().bind(heightProperty().divide(2).subtract(size));
hand.setStroke(color);
hand.setStrokeWidth(3);
return hand;
}
}
INFO : I've tried various other methods, like running an ExecutorService, using Task and Thread, but all yield same results.
Try this and see if you are having the same problem.
ClockGUI
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
/**
*
* #author Sedrick
*/
public class ClockGUI {
Circle clockFace;
Line second;
Line minute;
Line hour;
Rotate secondRotation;
Rotate minuteRotation;
Rotate hourRotation;
AnchorPane currentClockFace;
public ClockGUI()
{
currentClockFace = new AnchorPane();
currentClockFace.setPrefSize(100, 100);
clockFace = new Circle(100 / 2, 100 / 2, 100 / 2);
clockFace.setStroke(Color.BLACK);
clockFace.setFill(Color.TRANSPARENT);
second = new Line(100 / 2, 100 / 2, 100 / 2, 100 / 2 - 40);
secondRotation = new Rotate();
secondRotation.pivotXProperty().bind(second.startXProperty());
secondRotation.pivotYProperty().bind(second.startYProperty());
second.getTransforms().add(secondRotation);
minute = new Line(100 / 2, 100 / 2, 100 / 2, 100 / 2 - 30);
minuteRotation = new Rotate();
minuteRotation.pivotXProperty().bind(minute.startXProperty());
minuteRotation.pivotYProperty().bind(minute.startYProperty());
minute.getTransforms().add(minuteRotation);
hour = new Line(100 / 2, 100 / 2, 100 / 2, 100 / 2 - 20);
hourRotation = new Rotate();
hourRotation.pivotXProperty().bind(hour.startXProperty());
hourRotation.pivotYProperty().bind(hour.startYProperty());
hour.getTransforms().add(hourRotation);
currentClockFace.getChildren().addAll(clockFace, second, minute, hour);
}
public AnchorPane getCurrentClock()
{
return currentClockFace;
}
public void rotateSecondLine()
{
secondRotation.setAngle(secondRotation.getAngle() + 6);
}
public double getRotateSecondLine()
{
return secondRotation.getAngle();
}
public void setRotateSecond(double degree)
{
secondRotation.setAngle(degree);
}
public void rotateMinuteLine()
{
minuteRotation.setAngle(minuteRotation.getAngle() + 6);
}
public double getRotateMinuteLine()
{
return minuteRotation.getAngle();
}
public void setRotateMinute(double degree)
{
minuteRotation.setAngle(degree);
}
public void rotateHourLine()
{
hourRotation.setAngle(hourRotation.getAngle() + 6);
}
public double getRotateHourLine()
{
return hourRotation.getAngle();
}
public void setRotateHour(double degree)
{
hourRotation.setAngle(degree);
}
}
Main
import javafx.animation.*;
import javafx.application.*;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
import javafx.util.*;
/**
*
* #author Sedrick
*/
public class JavaFXApplication54 extends Application {
#Override
public void start(Stage primaryStage)
{
VBox root = new VBox();
ClockGUI cgui = new ClockGUI();
StackPane stackpane = new StackPane();
stackpane.getChildren().add(cgui.getCurrentClock());
root.getChildren().add(stackpane);
Button btn = new Button("Rotate seconds");
btn.setOnAction((event) -> {
cgui.rotateSecondLine();
});
Button btn2 = new Button("Rotate minutes");
btn2.setOnAction((event) -> {
cgui.rotateMinuteLine();
});
Button btn3 = new Button("Rotate hours");
btn3.setOnAction((event) -> {
cgui.rotateHourLine();
});
root.getChildren().addAll(btn, btn2, btn3);
Scene scene = new Scene(root, 300, 250);
Timeline timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
new EventHandler() {
// KeyFrame event handler
#Override
public void handle(Event event)
{
System.out.println(cgui.getRotateSecondLine());
cgui.rotateSecondLine();
if (cgui.getRotateSecondLine() >= 360) {
cgui.setRotateSecond(0);
cgui.rotateMinuteLine();
}
if (cgui.getRotateMinuteLine() >= 360) {
cgui.setRotateMinute(0);
cgui.rotateHourLine();
}
if (cgui.getRotateHourLine() >= 360) {
cgui.setRotateHour(0);
}
}
}
));
timeline.playFromStart();
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
Related
I have a JavaFX program in which I am looking to assign an event handler for a button but I want the event to trigger an action in a parent unit.
I have a GridPane as my main pane.
When I start my program, I have another Group of shapes (Called a Block) embedded in this gridpane at a fixed location (0,0).
As part of my Block, I have a Triangle button called DownArrow. My Plan is that when this button is pressed, the Doc controller will place a new Block beneath the one where the triangle was pressed.
I am looking at using the DownArrow.setOnMouseClicked event handler and map this back to the routine in the Controller to Add a new Block... However, I need some sort of info callback to let the controller know what Block was pressed.
I'm Stumped.
I was looking at maybe creating a custom event handler that I can pass more parameters but it seems clumsy - is there something else I should be doing?
How to pass paremeter with an event in javafx?
See Code Below in full:
package editorscratchpad;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.Font;
import javafx.stage.Stage;
/**
*
* #author a_curley
*/
public class EditorScratchPad extends Application {
HBox mainPanel;
VBox flowChart;
GridPane flowchartGrid;
VBox controlPanel;
Integer stepCount;
TextField descData;
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
descData = new TextField();
stepCount = 0;
btn.setText("Add Step");
btn.setOnAction((ActionEvent event) -> {
buttonClicked(event);
});
ScrollPane sp = new ScrollPane();
mainPanel = new HBox(20);
//mainPanel.setPrefWidth(400);
controlPanel = new VBox(5);
controlPanel.setAlignment(Pos.CENTER);
flowChart = new VBox();
flowChart.setPrefWidth(600);
flowChart.setAlignment(Pos.CENTER);
flowchartGrid = new GridPane();
flowchartGrid.setPrefWidth(600);
flowchartGrid.setHgap(10);
flowchartGrid.setVgap(10);
flowchartGrid.setPadding(new Insets(0, 10, 0, 10));
//sp.setContent(flowChart);
sp.setContent(flowchartGrid);
controlPanel.getChildren().add(btn);
controlPanel.getChildren().add(descData);
mainPanel.getChildren().add(sp);
mainPanel.getChildren().add(controlPanel);
Scene scene = new Scene(mainPanel, 800, 500);
primaryStage.sizeToScene();
primaryStage.setTitle("Flow chart");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public void buttonClicked(ActionEvent event) {
//Integer Step = stepCount*10;
//Integer Step = (flowChart.getChildren().size()+1)*10;
Integer Step = (flowchartGrid.getChildren().size() + 1);
String stateDesc = descData.getText();
if (stateDesc.length() < 3) {
stateDesc = "State " + Step.toString();
}
blockComponent newBlock = new blockComponent("S" + Integer.toString(Step * 10), stateDesc, Step); //<<<< Create the Block.
//flowChart.getChildren().add(newBlock.getComponent());
flowchartGrid.add(newBlock.getComponent(), 0, Step);
System.out.println("Added S" + Integer.toString(Step * 10));
stepCount++;
}
public void addNewBlock(ActionEvent event) {
Integer Step = (flowchartGrid.getChildren().size() + 1);
String stateDesc = "Step " + Integer.toString(Step * 10);
}
/**
* Subclass representing a Block Graphics only.
*/
public class blockComponent {
String Name;
String Desc;
Integer StateNo;
Integer XLoc;
Integer YLoc;
Integer blockHeight;
Integer blockWidth;
Integer stLabRad;
public blockComponent(String newName, String newDesc, Integer newSt) {
Name = newName;
Desc = newDesc;
StateNo = newSt;
XLoc = 0;
YLoc = 0;
blockHeight = 60;
blockWidth = 120;
stLabRad = 10;
}
public blockComponent(String newName, String newDesc, Integer newSt, Integer xCoOrd, Integer yCoOrd) {
this(newName, newDesc, newSt);
XLoc = xCoOrd;
YLoc = yCoOrd;
}
public Group getComponent() {
Group thisGroup = new Group();
// Define Rectangle
Rectangle Block = new Rectangle();
Block.setY(stLabRad);
Block.setX(stLabRad);
Block.setHeight(blockHeight);
Block.setWidth(blockWidth);
Block.setStroke(Color.BLACK);
Block.setStrokeWidth(2);
Block.setFill(Color.WHITESMOKE);
// Define state label
Circle stLab = new Circle();
stLab.setCenterX(stLabRad);
stLab.setCenterY(stLabRad);
stLab.setRadius(stLabRad);
stLab.setStroke(Color.PALEGREEN);
stLab.setFill(Color.PALEGREEN);
// Define State No.
Label stNo = new Label();
stNo.setFont(Font.font("Impact"));
stNo.setTextFill(Color.WHITE);
stNo.setLayoutX(0);
stNo.setLayoutY(0);
stNo.setText(StateNo.toString());
// Define the description
Label stD = new Label();
stD.setFont(Font.font("Impact"));
stD.setTextFill(Color.BLACK);
stD.setLayoutX(15);
stD.setLayoutY(15);
stD.setText(Desc.toString());
//---- Three Triangles for drawing. ----
// Down Arrow
Polygon downArrow = new Polygon();
downArrow.getPoints().addAll(new Double[]{
//X //Y
(blockWidth.doubleValue() / 2) + stLabRad, (blockHeight.doubleValue()),
((blockWidth.doubleValue() / 2) + 10) + stLabRad, (blockHeight.doubleValue() - 10),
((blockWidth.doubleValue() / 2) - 10) + stLabRad, (blockHeight.doubleValue() - 10)
// 70.0,60.0,
// 60.0,50.0,
// 80.0,50.0
});
downArrow.setStroke(Color.BLACK);
downArrow.setStrokeWidth(1);
downArrow.setFill(Color.LIGHTCYAN);
downArrow.setOnMouseEntered(new EventHandler<MouseEvent>() {
public void handle(MouseEvent me) {
downArrow.setStroke(Color.BLACK);
downArrow.setFill(Color.LIGHTCYAN);
}
});
downArrow.setOnMouseExited(new EventHandler<MouseEvent>() {
public void handle(MouseEvent me) {
downArrow.setStroke(Color.WHITESMOKE);
downArrow.setFill(Color.WHITESMOKE);
}
});
// try: https://stackoverflow.com/questions/35372236/how-to-pass-paremeter-with-an-event-in-javafx
//add all components to the group to display
thisGroup.getChildren().add(Block);
thisGroup.getChildren().add(stD);
thisGroup.getChildren().add(stLab);
thisGroup.getChildren().add(stNo);
thisGroup.getChildren().add(downArrow);
return thisGroup;
}
}
}
Not sure if this was the correct approach I was looking for but #0009laH response above for getSource() was the best that worked for me.
I have two classes ViewTaskTest and ControllerTaskTest. The view has two buttons, one to create a random point and the other to start the controller. After the start, the controller will place 10 random points to the chart inside the view. But the points are visible only at the end. I want to see the points being placed one by one. I know, that I have to use somehow the Task-functions and I am struggling to understand this concept.
This is the ViewTaskTest-class:
package View;
import Controller.ControllerTaskTest;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ViewTaskTest{
ScatterChart<Number, Number> scatterChart;
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
Axis<Number> xAxis = new NumberAxis(0, 10, 2); ;
Axis<Number> yAxis = new NumberAxis(0, 10, 2); ;
Button buttonAddRandomPoint = new Button("Add random point");
Button buttonStartControllerTest = new Button("Start controller");
private final int MAIN_WINDOW_HEIGHT = 800;
private final int MAIN_WINDOW_WIDTH = 800;
ControllerTaskTest controller;
public ViewTaskTest() {
}
public void setController(ControllerTaskTest controller) {
this.controller = controller;
}
public void fillStage(Stage stage) {
stage.setTitle("QuickPID");
stage.setMinHeight(MAIN_WINDOW_HEIGHT);
stage.setMinWidth(MAIN_WINDOW_WIDTH);
stage.setMaxHeight(MAIN_WINDOW_HEIGHT);
stage.setMaxWidth(MAIN_WINDOW_WIDTH);
Scene sceneOne = new Scene(new Group());
scatterChart = new ScatterChart<Number, Number>(xAxis, yAxis);
scatterChart.getData().add(series);
// add a random point
buttonAddRandomPoint.setOnAction(e -> {
Double x = new Double(0.0);
Double y = new Double(0.0);
try{
x = Math.random() * 10;
y = Math.random() * 10;
series.getData().add(new XYChart.Data<Number, Number>(x, y));
} catch (Exception ex) {
System.out.println("Error");
}
});
buttonStartControllerTest.setOnAction(e -> {
controller.startAddingPoints();
});
VBox vboxButtons = new VBox(5);
HBox hboxMain = new HBox(5);
vboxButtons.getChildren().addAll(buttonAddRandomPoint, buttonStartControllerTest);
vboxButtons.setAlignment(Pos.BOTTOM_LEFT);
vboxButtons.setPadding(new Insets(10));
hboxMain.getChildren().addAll(scatterChart, vboxButtons);
sceneOne.setRoot(hboxMain);
stage.setScene(sceneOne);
}
public void addRandomPointFromController(Double x, Double y) {
System.out.println("starting view task");
Task<Integer> task = new Task<Integer>() {
#Override protected Integer call() throws Exception {
series.getData().add(new XYChart.Data<Number, Number>(x, y));
return 0;
}
};
task.run();
}
}
Here is the ControllerTestTask-Class:
package Controller;
import View.ViewTaskTest;
import javafx.concurrent.Task;
public class ControllerTaskTest {
ViewTaskTest view;
public ControllerTaskTest() {
}
public void setView(ViewTaskTest view) {
this.view = view;
}
public void startAddingPoints() {
System.out.println("starting controller task");
Task<Integer> task = new Task<Integer>() {
#Override protected Integer call() throws Exception {
for(int i = 0; i < 10; i++) {
Double x = Math.random() * 10;
Double y = Math.random() * 10;
view.addRandomPointFromController(x, y);
System.out.println("Adding point x = " + x + " and y = " + y);
Thread.sleep(100);
}
return 0;
}
};
task.run();
}
}
Here is the main-class:
package Test;
import Controller.ControllerTaskTest;
import View.ViewTaskTest;
import javafx.application.Application;
import javafx.stage.Stage;
public class Test extends Application{
private ViewTaskTest view= new ViewTaskTest();
private ControllerTaskTest controller = new ControllerTaskTest();
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage) throws Exception {
view.setController(controller);
controller.setView(view);
stage.show();
view.fillStage(stage);
}
}
Some please explain me what I am doing wrong. I am experimenting with the Thread-funcitons but I cant solve my problem.
I'm trying to animate elements in GridPane. I have a class Unit that represents the things that I'm trying to move.
public class Unit {
private Text text;
private Rectangle rectangle;
private StackPane stackPane;
public Unit(Text text, Rectangle rectangle) {
this.text = text;
this.rectangle = rectangle;
text.setFill(Color.WHITE);
stackPane = new StackPane(rectangle, text);
}
public Text getText() {
return text;
}
public void setText(Text text) {
this.text = text;
}
public Rectangle getRectangle() {
return rectangle;
}
public void setRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
public StackPane getStackPane() {
return stackPane;
}
public void setStackPane(StackPane stackPane) {
this.stackPane = stackPane;
}
}
This is how i'm moving things now
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
GridPane gridPane = new GridPane();
gridPane.setVgap(5);
gridPane.setHgap(5);
Unit unit = new Unit(new Text("1"), new Rectangle(50, 50));
gridPane.add(unit.getStackPane(), 0, 0);
TranslateTransition translateTransition = new TranslateTransition();
translateTransition.setDuration(Duration.seconds(6));
translateTransition.setToX(200);
translateTransition.setToY(200);
translateTransition.setNode(unit.getStackPane());
translateTransition.play();
Scene scene = new Scene(gridPane, 300, 275);
primaryStage.setTitle("Hello World");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Is there a way to move the unit to a specific row - column location. I assume that gridpane might not be suitable for my purpose but it's easy way to layout things the way i want.
Here is an example based upon the layout animator at Animation upon layout changes (also in the gist https://gist.github.com/jewelsea/5683558). I don't really know if it is really what you are looking for (it might be pretty close). But in any case it is pretty neat ;-)
I won't explain it too much here as the main explanation on what it is and how it works is at the previously linked question.
The referenced Unit class is the one from your question.
AnimatedSparseGrid.java
import javafx.animation.*;
import javafx.application.Application;
import javafx.collections.*;
import javafx.geometry.Point2D;
import javafx.scene.*;
import javafx.scene.layout.GridPane;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
public class AnimatedSparseGrid extends Application {
private static final int NUM_UNITS = 10;
private static final int UNIT_SIZE = 30;
private static final int GRID_SIZE = 5;
private static final int GAP = 5;
private static final Duration PAUSE_DURATION = Duration.seconds(3);
private Random random = new Random(42);
private ObservableList<Unit> units = FXCollections.observableArrayList();
private GridPane gridPane = new GridPane();
#Override
public void start(Stage stage) throws Exception {
configureGrid();
LayoutAnimator animator = new LayoutAnimator();
animator.observe(gridPane.getChildren());
generateUnits();
relocateUnits();
continuouslyAnimateGrid();
stage.setScene(new Scene(gridPane));
stage.setResizable(false);
stage.show();
}
private void configureGrid() {
gridPane.setVgap(GAP);
gridPane.setHgap(GAP);
int size = GRID_SIZE * UNIT_SIZE + GAP * (GRID_SIZE - 1);
gridPane.setMinSize(size, size);
gridPane.setMaxSize(size, size);
}
private void generateUnits() {
for (int i = 0; i < NUM_UNITS; i++) {
Unit unit = new Unit(
new Text((i + 1) + ""),
new Rectangle(UNIT_SIZE, UNIT_SIZE)
);
units.add(unit);
}
}
private void relocateUnits() {
Set<Point2D> usedLocations = new HashSet<>();
for (Unit unit : units) {
Node node = unit.getStackPane();
int col;
int row;
do {
col = random.nextInt(GRID_SIZE);
row = random.nextInt(GRID_SIZE);
} while (usedLocations.contains(new Point2D(col, row)));
usedLocations.add(new Point2D(col, row));
GridPane.setConstraints(unit.getStackPane(), col, row);
if (!gridPane.getChildren().contains(node)) {
gridPane.add(node, col, row);
}
}
}
private void continuouslyAnimateGrid() {
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, event -> relocateUnits()),
new KeyFrame(PAUSE_DURATION)
);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Some what hackish, but you can move the unit to the desired row - column, measure the new X,Y and use it for the translation.
The following code demonstrates animating from 0,0 to 20,20 :
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private Button move;
private Pane unitPane, root;
private GridPane gridPane;
#Override
public void start(Stage primaryStage) throws Exception{
gridPane = new GridPane();
gridPane.setVgap(5);
gridPane.setHgap(5);
unitPane = new Unit(new Text("1"), new Rectangle(50, 50)).getStackPane();
gridPane.add(unitPane, 0, 0);
move = new Button("Move");
move.setOnAction(e->animate());
root = new BorderPane(gridPane, null, null, move, null);
Scene scene = new Scene(root, 300, 275);
primaryStage.setTitle("Hello World");
primaryStage.setScene(scene);
primaryStage.show();
}
private void animate() {
//remove unit, make it invisible, and add it to desired location
gridPane.getChildren().remove(unitPane);
unitPane.setVisible(false);
gridPane.add(unitPane, 20, 20);
root.layout(); //apply top down layout pass
//get x y of new location
double x = unitPane.getLayoutX(); double y = unitPane.getLayoutY(); //measure new location
//return to original location
gridPane.getChildren().remove(unitPane);
gridPane.add(unitPane, 0, 0);
unitPane.setVisible(true);
//apply translation to x,y of new location
TranslateTransition translateTransition = new TranslateTransition();
translateTransition.setDuration(Duration.seconds(3));
translateTransition.setToX(x);
translateTransition.setToY(y);
translateTransition.setNode(unitPane);
translateTransition.play();
//when translation is finished remove from original location
//add to desired location and set translation to 0
translateTransition.setOnFinished(e->{
gridPane.getChildren().remove(unitPane);
unitPane.setTranslateX(0);unitPane.setTranslateY(0);
gridPane.add(unitPane, 20, 20);
});
}
public static void main(String[] args) {
launch(args);
}
}
class Unit {
private Text text;
private Rectangle rectangle;
private StackPane stackPane;
Unit(Text text, Rectangle rectangle) {
this.text = text;
this.rectangle = rectangle;
text.setFill(Color.WHITE);
stackPane = new StackPane(rectangle, text);
}
public Text getText() {
return text;
}
public void setText(Text text) {
this.text = text;
}
public Rectangle getRectangle() {
return rectangle;
}
public void setRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
public StackPane getStackPane() {
return stackPane;
}
public void setStackPane(StackPane stackPane) {
this.stackPane = stackPane;
}
}
So I have written a Controller that is supposed to navigate between multiple scenes, however, when the second scene is instantiated java.lang.NullPointerException. Here is my Controller below with a View1() and View2() in a single file mre so you can understand what is happening. My goal is just to have multiple screens and multiple models and using a switch case set different scenes on the stage.
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.canvas.*;
import javafx.scene.image.Image;
public class Controller extends Application {
private Scene scene1, scene2;
public static void main(String[] args) {
launch(args);
}
public void start(Stage theStage) {
this.scene1 = new Scene(new Group(new View1()));
this.scene2 = new Scene(new Group(new View2()));
new AnimationTimer() {
int page = 2;
#Override
public void handle(long currentNanoTime){
// System.out.println(currentNanoTime);
switch (page){
case 2:
page = 1;
theStage.setScene(scene1);
break;
case 1:
page = 2;
theStage.setScene(scene2);
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
theStage.show();
}
}
class View1 extends Group {
public View1() {
Image img = new Image("https://i.imgur.com/8tcxHWh.jpg");
Canvas canvas = new Canvas(img.getWidth(), img.getHeight());
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
getChildren().add(canvas);
}
}
class View2 extends Group {
public View2() {
Image img = new Image("https://i.imgur.com/BF3ty6o.jpg");
Canvas canvas = new Canvas(img.getWidth(), img.getHeight());
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
getChildren().add(canvas);
}
}
I would suggest against using AnimationTimer. I would suggest you use Buttons to load different displays. This app demos one way of using Buttons to switch between displays.
Main
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
final StackPane mainDisplay = new StackPane();
final ViewOne viewOne = new ViewOne();
final ViewTwo viewTwo = new ViewTwo();
mainDisplay.getChildren().add(viewOne);//Load first view.
Button btnStageOne = new Button("View One");
Button btnStageTwo = new Button("View Two");
btnStageOne.setOnAction((event) -> {
if(!mainDisplay.getChildren().get(0).equals(viewOne))//If sceneone is not loaded, load it.
{
mainDisplay.getChildren().set(0, viewOne);
}
});
btnStageTwo.setOnAction((event) -> {
if(!mainDisplay.getChildren().get(0).equals(viewTwo))//If scenetwo is not loaded, load it.
{
mainDisplay.getChildren().set(0, viewTwo);
}
});
HBox hbButtonPanel = new HBox(btnStageOne, btnStageTwo);
VBox root = new VBox(mainDisplay, hbButtonPanel);
Scene scene = new Scene(root);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ViewOne
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
/**
*
* #author sedrick
*/
public final class ViewOne extends StackPane{
Label label = new Label();
public ViewOne() {
label.setText("Scene One!");
getChildren().add(label);
}
}
ViewTwo
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
/**
*
* #author sedrick
*/
public final class ViewTwo extends StackPane{
Label label = new Label();
public ViewTwo() {
label.setText("Scene Two!");
getChildren().add(label);
}
}
I need some help if you can spare a few minutes.
I am in a bit of a pickle as I try to make this work.
I have a javaFX class like this
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FoorballTeam extends Application {
int i1=0;
int i3=0;
String[] PlayerNames = new String[12];
int[] goals = new int[12];
#Override
public void start(Stage primaryStage) {
player[] playerData = new player[12];
Button btn = new Button();
btn.setText("add Player");
GridPane root = new GridPane();
root.add(btn,0,0);
int i2;
for (i2=0;i2<=11;i2++)
{playerData[i2]=new player();}
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
playerData[i3].player(root, i3);
i3++;
}
});
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public String[] getPlayerNames() {
return PlayerNames;
}
public void setPlayerNames(String[] PlayerNames) {
this.PlayerNames = PlayerNames;
}
public int[] getGoals() {
return goals;
}
public void setGoals(int[] goals) {
this.goals = goals;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
and a second class named player like this
import javafx.event.EventType;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
public class player {
String NameOfPlayer = new String();
int goalsOfPlayer;
public void player (GridPane root,int numberOfPlayer)
{
TextField name = new TextField();
TextField goals = new TextField();
GridPane grid = new GridPane();
grid.add(name,0,0);
grid.add(goals,1,0);
root.add(grid,0,numberOfPlayer+1);
System.out.println("player " + numberOfPlayer + " added");
name.textProperty().addListener((observable, oldValue, newValue) -> {
NameOfPlayer=newValue;
});
goals.textProperty().addListener((observable, oldValue, newValue) -> {
goalsOfPlayer=Integer.parseInt(newValue);
});
}
}
I want every time that I make a change to a players name or goals to pass this change on the two arrays PlayerNames[] and goals[] of the main class.
for example if player1 changes goals from 1 to 2 I want the goals[1]=2.
Also is it possible to put a listener to this two arrays so when a player changes name or goals to trigger the listener.
Any help will be appreciated.
One simple solution is to warp the int[] array with an observable list, and listen to changes in this list :
import java.util.Arrays;
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class ListenToArrayCahnges extends Application {
private final int SIZE = 12;
private final Integer[] goals = new Integer[SIZE];
private final Random rand = new Random();
private int counter = 0;
#Override
public void start(Stage primaryStage) {
Arrays.fill(goals, 0); //initial values
//Warp array with an observable list. list back by array so it is of fixed length
ObservableList<Integer> goalsList = FXCollections.observableArrayList(Arrays.asList(goals));
//add listener to list
goalsList.addListener((ListChangeListener<Integer>) c ->{
//respond to list changes
System.out.println("Goals changed to : "+ goalsList);
});
//button to change the list
Button btn = new Button();
btn.setText("Add goal");
btn.setOnAction(event -> {
goalsList.set(counter, rand.nextInt(100));
counter = ++counter % SIZE ; //increment counter 0,1,2....11 and back to 0
});
GridPane root = new GridPane();
root.add(btn,0,0);
Scene scene = new Scene(root, 150, 50);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Note that the posted mcve represents the problem that needs to be solved (or in this case a solution to it), and not the specific application or use case.