JavaFX - Event Handlers on different objects created by for loop - javafx

I want to create a set of ten different circles with a for loop and have each of them change color when the mouse cursor hovers over one of them and also have them change to a third color with a mouse click. However only one of the circles - the last one to be created in the loop- has the color changes, regardless which circle gets clicked or hovered over. Can anyone explain me why and how can I fix this? I would be very greatful. Hier is is my code:
public class View extends Parent{
BorderPane gameScreen;
Group hexaBlock;
ArrayList<Circle> circleList = new ArrayList<>();
Circle circle;
...
public View(){
gameScreen = new BorderPane();
hexaBlock = new Group();
...
for(int y=0; y<2; y++ ){
for(double x=0; x<5; x++){
circle = new Circle(xPosition(hexagon width*x), yPosition(hexagon height*4*y), radius);
circleList.add(circle);
circle.setFill(Color.BLACK);
circle.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.setFill(Color.CYAN);
}
});
circle.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent t) {
circle.setFill(Color.RED);
}
});
circle.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent t) {
circle.setFill(Color.BLACK);
}
});
}
this.getChildren().add(gameScreen);
...
gameScreen.setCenter(hexaBlock);
...
hexaBlock.getChildren().addAll(circleList);
.....

circle is a field. When the event handlers are run, the field's value is retrieved and in this case it contains the value last assigned to it, i.e. the circle created last.
Note that you can access final (effectively final for java >= 8) local variables in surrounding scopes from anonymus classes. I recommend removing the circle field and declaring circle where you assign the value to it:
for(int y=0; y<2; y++ ){
for(double x=0; x<5; x++){
final Circle circle = new Circle(xPosition(hexagon width*x), yPosition(hexagon height*4*y), radius);
...
// init circle handlers/properties
}
}

Here is a sample app. This app uses lambdas for the listeners.
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication54 extends Application
{
#Override
public void start(Stage primaryStage)
{
Random random = new Random();
AnchorPane root = new AnchorPane();
for (int i = 0; i < 5; i++)
{
int x1 = random.nextInt(300);
System.out.println("l: " + x1);
int y1 = random.nextInt(250);
int radius = random.nextInt(10) + 3;
root.getChildren().add(getCircle(x1, y1, radius));
}
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
Circle getCircle(int x1, int y1, int radius)
{
Circle tempCircle = new Circle(x1, y1, radius);
tempCircle.setFill(Color.BLACK);
tempCircle.setOnMousePressed(me -> tempCircle.setFill(Color.CYAN));
tempCircle.setOnMouseEntered(me -> tempCircle.setFill(Color.RED));
tempCircle.setOnMouseExited(me -> tempCircle.setFill(Color.BLACK));
return tempCircle;
}
}

This is my take on it, uncomment the setOnMouseExited if you want it to turn black again.
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class CircleColours extends Application {
private final Random random = new Random();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
AnchorPane pane = new AnchorPane();
Scene scene = new Scene(pane, 600, 400);
addCircles(pane, 10, 50);
primaryStage.setScene(scene);
primaryStage.show();
}
public void addCircles(AnchorPane pane, int amount, int radius) {
for (int i = 0; i < amount; i++) {
Circle circle = new Circle(random.nextInt((int) pane.getWidth()), random.nextInt((int) pane.getHeight()), radius);
circle.setOnMouseEntered(event -> circle.setFill(Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255))));
circle.setOnMouseClicked(event -> circle.setFill(Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255))));
//circle.setOnMouseExited(event -> circle.setFill(Color.BLACK));
pane.getChildren().add(circle);
}
}
}

Related

Click to draw a shape (circle)

this code Draw a circle and I want to put a button that when I click on the button I can do draw a circle. How Can I put Button for code below?
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class HelloApplication extends Application {
private Circle circle;
private boolean firstClick = true;
private double centerX, centerY;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
circle = new Circle();
circle.setFill(Color.TRANSPARENT);
circle.setStroke(Color.BLACK);
root.getChildren().add(circle);
Scene scene = new Scene(root, 600, 600);
setHandlers(scene);
primaryStage.setTitle("blank");
primaryStage.setScene(scene);
primaryStage.show();
}
public void setHandlers(Scene scene) {
scene.setOnMouseClicked(e -> {
if (firstClick) {
centerX = e.getX();
centerY = e.getY();
// sets the center
circle.setCenterX(centerX);
circle.setCenterY(centerY);
// sets next "stage" of drawing your circle
firstClick = false;
// on second click will reset the process by setting firstClick to true
}
else {
firstClick = true;
}
});
scene.setOnMouseMoved(e -> {
// will only evaluate on the first instance of a click
if (!firstClick) {
// Distance formula between center of circle and mouse pointer
double c = Math
.sqrt(Math.pow(centerX - e.getX(), 2) + Math.pow(centerY - e.getY(), 2));
circle.setRadius(c);
}
});
}
}
The following mre demonstrates the basic functionality you required :
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
public class DrawCircles extends Application {
private Pane drawPane; //container for shapes
private Circle clickedPoint;
private static Color POINT_COLOR = Color.BLUEVIOLET, CIRCLE_COLOR = Color.RED;
private static int POINT_RADIUS = 2;
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
drawPane = new Pane(); //container for shapes
root.setCenter(drawPane);
Label help = new Label();
ToggleButton draw = new ToggleButton(" Draw ");
draw.selectedProperty().addListener((ChangeListener<Boolean>) (obs, oldV, newV) -> {
draw.setText(newV ? "Drawing" : " Draw ");
help.setText(newV ? "Double click on one point. Double click on second point" : "");
});
HBox buttonBar = new HBox(10, draw, help);
buttonBar.setAlignment(Pos.BOTTOM_LEFT);
root.setBottom(buttonBar);
drawPane.setOnMouseClicked(event -> {
if(!draw.isSelected()) return;
if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) {
addPoint( event.getX() , event.getY());
}
});
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.setTitle("Draw Circles");
primaryStage.show();
}
private void addPoint(double x, double y) {
if(clickedPoint == null){ //no previously clicked point
clickedPoint = new Circle(x, y, POINT_RADIUS, POINT_COLOR);
drawPane.getChildren().add(clickedPoint); //mark clicked point
}else{
Shape shape = makeCircle(clickedPoint.getCenterX(), clickedPoint.getCenterY(), x, y);
drawPane.getChildren().add(shape); //add line
drawPane.getChildren().remove(clickedPoint);// remove first clicked point
clickedPoint = null;
}
}
private Shape makeCircle(double xCenter, double yCenter, double xEdge, double yEdge){
double radius =Math.sqrt( Math.pow(xEdge - xCenter, 2) + Math.pow(yEdge - yCenter, 2));
return new Circle(xCenter, yCenter, radius, CIRCLE_COLOR);
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX Pan and Zoom with Draggable Nodes Inside

I have a simple JavaFX pan and zoom application as show below. The pan and zoom functionality work great, but I would also like to be able to drag and drop the circle node too. The problem I have is that the scrollpane gets all of the mouse events first, so I'm unable to assign a drag and drop to just the circle. Is it possible to have a draggable/zoomable scrollpane and also be able to drag a node inside the pane?
Screenshot
Here us the code that I'm using:
package sample;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private ScrollPane scrollPane = new ScrollPane();
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
private final Group group = new Group();
ImageView bigImageView = null;
PanAndZoomPane panAndZoomPane = null;
Pane featuresPane = new Pane();
#Override
public void start(Stage primaryStage) throws Exception{
bigImageView = new ImageView();
StackPane bigStackpane = new StackPane();
bigStackpane.getChildren().add(bigImageView);
bigStackpane.getChildren().add(featuresPane);
featuresPane.toFront();
featuresPane.setOpacity(.8);
Circle circle = new Circle();
circle.setCenterX(200);
circle.setCenterY(200);
circle.setRadius(100);
circle.setFill(Color.RED);
circle.setOnMouseClicked(e -> {
System.out.println("circle clicked");
});
featuresPane.getChildren().add(circle);
scrollPane.setPannable(true);
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
group.getChildren().add(bigImageView);
group.getChildren().add(bigStackpane);
panAndZoomPane = new PanAndZoomPane();
zoomProperty.bind(panAndZoomPane.myScale);
deltaY.bind(panAndZoomPane.deltaY);
panAndZoomPane.getChildren().add(group);
SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
scrollPane.setContent(panAndZoomPane);
panAndZoomPane.toBack();
scrollPane.addEventFilter( MouseEvent.MOUSE_CLICKED, sceneGestures.getOnMouseClickedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scrollPane.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
AnchorPane bigImageAnchorPane = new AnchorPane();
bigImageAnchorPane.getChildren().add(scrollPane);
Image image = new Image("https://i.imgur.com/8p1XBag.jpg");
bigImageView.setImage(image);
bigImageAnchorPane.setTopAnchor(scrollPane, 1.0d);
bigImageAnchorPane.setRightAnchor(scrollPane, 1.0d);
bigImageAnchorPane.setBottomAnchor(scrollPane, 1.0d);
bigImageAnchorPane.setLeftAnchor(scrollPane, 1.0d);
BorderPane root = new BorderPane(bigImageAnchorPane);
Label label = new Label("Pan and Zoom Test");
root.setTop(label);
Scene scene = new Scene(root, 1000, 1000);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
class PanAndZoomPane extends Pane {
public static final double DEFAULT_DELTA = 1.5d; //1.3d
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
private Timeline timeline;
public PanAndZoomPane() {
this.timeline = new Timeline(30);//60
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
public double getScale() {
return myScale.get();
}
public void setScale( double scale) {
myScale.set(scale);
}
public void setPivot( double x, double y, double scale) {
// note: pivot value must be untransformed, i. e. without scaling
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(100), new KeyValue(translateXProperty(), getTranslateX() - x)), //200
new KeyFrame(Duration.millis(100), new KeyValue(translateYProperty(), getTranslateY() - y)), //200
new KeyFrame(Duration.millis(100), new KeyValue(myScale, scale)) //200
);
timeline.play();
}
public double getDeltaY() {
return deltaY.get();
}
public void setDeltaY( double dY) {
deltaY.set(dY);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
public class SceneGestures {
private DragContext sceneDragContext = new DragContext();
PanAndZoomPane panAndZoomPane;
public SceneGestures( PanAndZoomPane canvas) {
this.panAndZoomPane = canvas;
}
public EventHandler<MouseEvent> getOnMouseClickedEventHandler() {
return onMouseClickedEventHandler;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
sceneDragContext.mouseAnchorX = event.getX();
sceneDragContext.mouseAnchorY = event.getY();
sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = PanAndZoomPane.DEFAULT_DELTA;
double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
panAndZoomPane.setDeltaY(event.getDeltaY());
if (panAndZoomPane.deltaY.get() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale)-1;
double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth()/2 + panAndZoomPane.getBoundsInParent().getMinX()));
double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight()/2 + panAndZoomPane.getBoundsInParent().getMinY()));
panAndZoomPane.setPivot(f*dx, f*dy, scale);
event.consume();
}
};
/**
* Mouse click handler
*/
private EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton().equals(MouseButton.PRIMARY)) {
if (event.getClickCount() == 2) {
System.out.println("Image Layer Double Clicked...");
}else{
System.out.println("Image Layer Clicked...");
}
}
}
};
}
}
Your code is adding behavior via event filters. These filters are invoked during the event capturing phase which means they are invoked before the events reach your circle. You should strive to implement your behavior via event handlers, which are invoked during the event bubbling phase. Then you can consume events to prevent them from reaching ancestors, allowing you to drag your circle without scrolling/panning the scroll-pane content. For more information about event handling and propagation, check out this tutorial.
Here's a proof-of-concept which adds the zoom-handling to the scroll-pane's content and still let's you drag around a circle:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
// using your example image
ImageView imageView = new ImageView("https://i.imgur.com/8p1XBag.jpg");
Circle circle = new Circle(100, 100, 25, Color.FIREBRICK);
circle.setOnMousePressed(
e -> {
// prevent pannable ScrollPane from changing cursor on drag-detected (implementation
// detail)
e.setDragDetect(false);
Point2D offset =
new Point2D(e.getX() - circle.getCenterX(), e.getY() - circle.getCenterY());
circle.setUserData(offset);
e.consume(); // prevents MouseEvent from reaching ScrollPane
});
circle.setOnMouseDragged(
e -> {
// prevent pannable ScrollPane from changing cursor on drag-detected (implementation
// detail)
e.setDragDetect(false);
Point2D offset = (Point2D) circle.getUserData();
circle.setCenterX(e.getX() - offset.getX());
circle.setCenterY(e.getY() - offset.getY());
e.consume(); // prevents MouseEvent from reaching ScrollPane
});
// the zoom-able content of the ScrollPane
Group group = new Group(imageView, circle);
// wrap Group in another Group since it's the former that's scaled and
// Groups only take transformations of their **children** into account (not themselves)
StackPane content = new StackPane(new Group(group));
content.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
// due to later configuration, the StackPane will always cover the entire viewport
content.setOnScroll(
e -> {
if (e.isShortcutDown() && e.getDeltaY() != 0) {
if (e.getDeltaY() < 0) {
group.setScaleX(Math.max(group.getScaleX() - 0.1, 0.5));
} else {
group.setScaleX(Math.min(group.getScaleX() + 0.1, 5.0));
}
group.setScaleY(group.getScaleX());
e.consume(); // prevents ScrollEvent from reaching ScrollPane
}
});
// use StackPane (or some other resizable node) as content since Group is not
// resizable. Note StackPane will center content if smaller than viewport.
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setPannable(true);
// ensure StackPane content always has at least the same dimensions as the viewport
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
primaryStage.setScene(new Scene(scrollPane, 1000, 650));
primaryStage.show();
}
}
Note this does not exactly replicate the behavior of your example. It does not use animations nor does it zoom on a pivot point. But hopefully it can help you move forward in your application.

High refreshing rate in JavaFX

I'm trying to write a program with an equalizer, a frequency analyzer and a sound level meter. The model part seems to work very well but I'm experimenting some bugs with the IHM.
My last bug is with the level meter. After a while (from few milliseconds to few seconds), it freezes and don't update anymore. So, here is a (simplified) version of it. I added the runnable part to test and reproduce the bug. Of course, this bug appears sooner when I add other graphical components which also need to refresh very frequently. For example, the frequency analyze is represented by a line-chart with something like 1000 points.
public class LevelMeter2 extends Parent implements Runnable {
private IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
private Rectangle led;
private IntegerProperty height = new SimpleIntegerProperty();
private IntegerProperty width = new SimpleIntegerProperty();
private DoubleProperty linearValue = new SimpleDoubleProperty();
private Color backgroundColor=Color.BLACK;
private double minLinearValue, maxLinearValue;
public LevelMeter2 (int height2, int width2) {
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0/100);
maxLinearValue = Math.pow(10, 3.0/100)-minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
this.getChildren().add(led);
}
public double convertdBToLinearValue (double dB) {
return ((double)Math.round(100 * ((Math.pow(10, dB/100)-minLinearValue)/maxLinearValue)) ) /100 ;
//return (Math.pow(10, dB/100)-minLinearValue)/maxLinearValue;
}
public double convertLinearValueTodB (double linearValue) {
return 100*Math.log10(linearValue*maxLinearValue+minLinearValue);
}
public void setValue (double dB) {
if (dB>3) {
dB=3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
#Override
public void run() {
int i = 0;
double value=-20;
while (i<1000) {
setValue(value);
value = (Math.random()-0.5)*10+value;
if (value>3) {
value=3;
}
if (value<-60) {
value=-60;
}
i++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("END OF WHILE");
}
}
And a "Main" to test it :
public class MainGraph extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
HBox pane = new HBox();
LevelMeter2 levelMeter = new LevelMeter2(300,30);
Thread t = new Thread(levelMeter);
pane.getChildren().add(levelMeter);
t.start();
Scene scene = new Scene(pane, 300, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest( event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
}
What's wrong with my code ? How can I write a more robust code that will allow me high refresh rates of my IHM ? Or how can I prevent from freezing ?
Thank you for you help.
I would suggest you move away from Threads and use something from JavaFX Animation package. In this example Timeline is used. This code is set to run at a rate of about 60 fps. You can adjust that using Duration.millis().
Main
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication342 extends Application
{
#Override
public void start(Stage primaryStage)
{
LevelMeter2 levelMeter = new LevelMeter2(300, 30);
Button button = new Button("Start");
button.setOnAction((event) -> {
switch (button.getText()) {
case "Start":
levelMeter.startAnimation();
button.setText("Stop");
break;
case "Stop":
levelMeter.stopAnimation();
button.setText("Start");
break;
}
});
HBox pane = new HBox(levelMeter, button);
Scene scene = new Scene(pane, 300, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest(event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
LevelMeter2
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public final class LevelMeter2 extends Parent
{
private final IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
Timeline timeline;
double value = -20;
private final Rectangle led;
private final IntegerProperty height = new SimpleIntegerProperty();
private final IntegerProperty width = new SimpleIntegerProperty();
private final DoubleProperty linearValue = new SimpleDoubleProperty();
private final Color backgroundColor = Color.BLACK;
private final double minLinearValue;
private final double maxLinearValue;
public LevelMeter2(int height2, int width2)
{
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0 / 100);
maxLinearValue = Math.pow(10, 3.0 / 100) - minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
getChildren().add(led);
timeline = new Timeline(new KeyFrame(Duration.millis(16), (event) -> {
setValue(value);
value = (Math.random() - 0.5) * 10 + value;
if (value > 3) {
value = 3;
}
if (value < -60) {
value = -60;
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
}
public double convertdBToLinearValue(double dB)
{
return ((double) Math.round(100 * ((Math.pow(10, dB / 100) - minLinearValue) / maxLinearValue))) / 100;
}
public double convertLinearValueTodB(double linearValue)
{
return 100 * Math.log10(linearValue * maxLinearValue + minLinearValue);
}
public void setValue(double dB)
{
if (dB > 3) {
dB = 3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
public void startAnimation()
{
timeline.play();
}
public void stopAnimation()
{
timeline.stop();
}
}
Multiple LevelMeters Example:
Main
import java.util.ArrayList;
import java.util.List;
import javafx.animation.ParallelTransition;
import javafx.animation.Timeline;
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;
/**
*
* #author blj0011
*/
public class JavaFXApplication342 extends Application
{
#Override
public void start(Stage primaryStage)
{
List<LevelMeter2> levelMeter2s = new ArrayList();
List<Timeline> metersTimelines = new ArrayList();
for (int i = 0; i < 9; i++) {
LevelMeter2 levelMeter2 = new LevelMeter2(300, 30);
levelMeter2s.add(levelMeter2);
metersTimelines.add(levelMeter2.getTimeline());
}
ParallelTransition parallelTransition = new ParallelTransition();
parallelTransition.getChildren().addAll(metersTimelines);
Button button = new Button("Start");
button.setOnAction((event) -> {
switch (button.getText()) {
case "Start":
parallelTransition.play();
button.setText("Stop");
break;
case "Stop":
parallelTransition.stop();
button.setText("Start");
break;
}
});
HBox hBox = new HBox();
hBox.getChildren().addAll(levelMeter2s);
VBox vBox = new VBox(hBox, new StackPane(button));
Scene scene = new Scene(vBox, 300, 350);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest(event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
LevelMeter2
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public final class LevelMeter2 extends Parent
{
private final IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
Timeline timeline;
double value = -20;
private final Rectangle led;
private final IntegerProperty height = new SimpleIntegerProperty();
private final IntegerProperty width = new SimpleIntegerProperty();
private final DoubleProperty linearValue = new SimpleDoubleProperty();
private final Color backgroundColor = Color.BLACK;
private final double minLinearValue;
private final double maxLinearValue;
public LevelMeter2(int height2, int width2)
{
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0 / 100);
maxLinearValue = Math.pow(10, 3.0 / 100) - minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
getChildren().add(led);
timeline = new Timeline(new KeyFrame(Duration.millis(25), (event) -> {
setValue(value);
value = (Math.random() - 0.5) * 10 + value;
if (value > 3) {
value = 3;
}
if (value < -60) {
value = -60;
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
}
public double convertdBToLinearValue(double dB)
{
return ((double) Math.round(100 * ((Math.pow(10, dB / 100) - minLinearValue) / maxLinearValue))) / 100;
}
public double convertLinearValueTodB(double linearValue)
{
return 100 * Math.log10(linearValue * maxLinearValue + minLinearValue);
}
public void setValue(double dB)
{
if (dB > 3) {
dB = 3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
public void startAnimation()
{
timeline.play();
}
public void stopAnimation()
{
timeline.stop();
}
public Timeline getTimeline()
{
return timeline;
}
}
Your implementation of run() appears to be updating the scene graph from a background thread. As discussed in Concurrency in JavaFX:
The JavaFX scene graph…is not thread-safe and can only be accessed and modified from the UI thread also known as the JavaFX Application thread. Implementing long-running tasks on the JavaFX Application thread inevitably makes an application UI unresponsive."
Instead, use a Task, illustrated here and here. Your implementation of call() can collect data asynchronously and notify the GUI of the current state via updateValue(). Your valueProperty() listener can then invoke setValue() safely. Because "Updates are coalesced to prevent saturation of the FX event queue," your application will perform satisfactorily even on older hardware.
Alternatively, if your audio source is one of the supported Media types, AudioBarChartApp, also seen here, updates the data model of a BarChart in an AudioSpectrumListener registered with the corresponding MediaPlayer. The image below displays pink noise.
private XYChart.Data<String, Number>[] series1Data;
…
audioSpectrumListener = (double timestamp, double duration,
float[] magnitudes, float[] phases) -> {
for (int i = 0; i < series1Data.length; i++) {
series1Data[i].setYValue(magnitudes[i] + 60);
}
};

JavaFX : how to have my title and menu centered on top of each other [WITH MVCE]

I'm new to JavaFX and I'm trying to make a menu that can be any size.
I've tried every layout possible for hours but I can't get a simple design done.
My background is a black Rectangle. I want the title to be centered on top of the screen, and my menu to be centered below the title.
Plus I want the stage size to be fixed to the Rectangle size, so that we don't see white on the background.
Here's my mvce :
package mvce_poneymon_menu;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Mvce_poneymon_menu extends Application {
#Override
public void start(Stage stage) throws Exception {
MenuView menuView = new MenuView(600, 600);
Group root = new Group();
Scene scene = new Scene(root);
stage.setTitle("Poneymon");
stage.setScene(scene);
root.getChildren().add(menuView);
menuView.requestFocus();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MenuView.java :
package mvce_poneymon_menu;
import javafx.animation.TranslateTransition;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
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.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.util.Duration;
public class MenuView extends StackPane {
static final Font FONT = Font.font("", FontWeight.BOLD, 50);
int width;
int height;
VBox menuBox;
int currentItem = 0;
public MenuView(int w, int h) {
width = w;
height = h;
createContent();
setOnKeyPressedEvent();
}
private void createContent() {
GridPane grid = new GridPane();
MenuItem exitItem = new MenuItem("Exit");
exitItem.setOnActivate(() -> System.exit(0));
menuBox = new VBox(10,
new MenuItem("Start a game"),
new MenuItem("Parameters"),
exitItem);
menuBox.setAlignment(Pos.CENTER);
menuBox.setTranslateX(360);
getMenuItem(0).setActive(true);
HBox title = (HBox)createTitle("Poneymon");
grid.add(title, 0, 0);
grid.add(menuBox, 0, 1);
Rectangle bg = new Rectangle(width, height);
grid.setTranslateY(25);
this.getChildren().addAll(bg, grid);
}
private Node createTitle(String title) {
HBox letters = new HBox(0);
letters.setAlignment(Pos.CENTER);
for (int i = 0; i < title.length(); i++) {
Text letter = new Text(title.charAt(i) + "");
letter.setFont(FONT);
letter.setFill(Color.WHITE);
letters.getChildren().add(letter);
TranslateTransition tt = new TranslateTransition(Duration.seconds(2), letter);
tt.setDelay(Duration.millis(i * 50));
tt.setToY(-25);
tt.setAutoReverse(true);
tt.setCycleCount(TranslateTransition.INDEFINITE);
tt.play();
}
return letters;
}
private MenuItem getMenuItem(int index) {
return (MenuItem)menuBox.getChildren().get(index);
}
private void setOnKeyPressedEvent() {
this.setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(KeyEvent e) {
if (e.getCode() == KeyCode.UP) {
if (currentItem > 0) {
getMenuItem(currentItem).setActive(false);
getMenuItem(--currentItem).setActive(true);
}
}
if (e.getCode() == KeyCode.DOWN) {
if (currentItem < menuBox.getChildren().size() - 1) {
getMenuItem(currentItem).setActive(false);
getMenuItem(++currentItem).setActive(true);
}
}
if (e.getCode() == KeyCode.ENTER) {
getMenuItem(currentItem).activate();
}
}
});
}
}
MenuItem.java :
package mvce_poneymon_menu;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
public class MenuItem extends HBox {
static final Font FONT = Font.font("", FontWeight.BOLD, 30);
private TriCircle c1 = new TriCircle();
private TriCircle c2 = new TriCircle();
private Text text;
private Runnable script;
private static class TriCircle extends Parent {
public TriCircle() {
Shape shape1 = Shape.subtract(new Circle(5), new Circle(2));
shape1.setFill(Color.WHITE);
Shape shape2 = Shape.subtract(new Circle(5), new Circle(2));
shape2.setFill(Color.WHITE);
shape2. setTranslateX(5);
Shape shape3 = Shape.subtract(new Circle(5), new Circle(2));
shape3.setFill(Color.WHITE);
shape3.setTranslateX(2.5);
shape3.setTranslateY(-5);
getChildren().addAll(shape1, shape2, shape3);
setEffect(new GaussianBlur(2));
}
}
public MenuItem(String name) {
super(15);
setAlignment(Pos.CENTER);
text = new Text(name);
text.setFont(FONT);
text.setEffect(new GaussianBlur(2));
getChildren().addAll(c1, text, c2);
setActive(false);
setOnActivate(() -> System.out.println(name + " activated"));
}
public void setActive(boolean b) {
c1.setVisible(b);
c2.setVisible(b);
text.setFill(b ? Color.WHITE : Color.GREY);
}
public void setOnActivate(Runnable r) {
script = r;
}
public void activate() {
if (script != null) {
script.run();
}
}
}
I'm sure this is very simple but I can't figure it out :c
My background is a black Rectangle. [...] Plus I want the stage size to be fixed to the Rectangle size, so that we don't see white on the background.
It would be much simpler to simply assign a background to the StackPane. This would allow you to resize MenuView and keep the size of the background the same as the size of the MenuView without additional logic.
Preventing resizing of the window should be done for the Stage using setResizable.
I want the title to be centered on top of the screen, and my menu to be centered below the title.
You're using an "unhealthy" amount of transformation properties. (I'm refering to translateX and translateY in this case.) These properties are not taken into account by the parent layout; during the layout the nodes are positioned where the same node without any transformation would be positioned and the rendering algorithm considers those transformations though.
Imho the following structure would suit the desired outcome better:
MenuView (root)
|- VBox (place menu items below title)
|- HBox (title container)
|- ...
|- MenuItem
|- MenuItem
|- MenuItem
To get the correct size for the title container, I recommend using a padding around the content.
There are several other things that I'd change:
Shape shape1 = Shape.subtract(new Circle(5), new Circle(2));
shape1.setFill(Color.WHITE);
I'd recommend changing this to circles with a stroke instead oc intersecting shapes.
Instead of blurring every child in MenuItem seperatly I'd recommend applying a blur on the item itself.
The TriCircle class does not contain any logic other than setting up the nodes. It could (and should) be replaced by a method creating a Group containing the circles.
#Override
public void start(Stage stage) {
MenuView menuView = new MenuView(600, 600);
Scene scene = new Scene(menuView);
stage.setTitle("Poneymon");
stage.setScene(scene);
menuView.requestFocus();
stage.setResizable(false); // prevent resizing of stage
stage.show();
}
public class MenuView extends StackPane {
static final Font FONT = Font.font("", FontWeight.BOLD, 50);
int currentItem = 0;
public MenuView(int w, int h) {
setPrefSize(w, h);
createContent();
setOnKeyPressedEvent();
}
private List<MenuItem> menuItems;
private void createContent() {
MenuItem exitItem = new MenuItem("Exit");
exitItem.setOnActivate(() -> Platform.exit());
menuItems = Arrays.asList(
new MenuItem("Start a game"),
new MenuItem("Parameters"),
exitItem);
VBox container = new VBox(10, createTitle("Poneymon"));
container.getChildren().addAll(menuItems);
container.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
getMenuItem(0).setActive(true);
setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
getChildren().add(container);
}
private Node createTitle(String title) {
final double movement = 25;
HBox letters = new HBox();
letters.setAlignment(Pos.CENTER);
// add space on top equla to the upwards movement of the letters
letters.setPadding(new Insets(movement, 0, 0, 0));
for (int i = 0; i < title.length(); i++) {
Text letter = new Text(title.charAt(i) + "");
letter.setFont(FONT);
letter.setFill(Color.WHITE);
letters.getChildren().add(letter);
TranslateTransition tt = new TranslateTransition(Duration.seconds(2), letter);
tt.setDelay(Duration.millis(i * 50));
tt.setToY(-movement);
tt.setAutoReverse(true);
tt.setCycleCount(TranslateTransition.INDEFINITE);
tt.play();
}
return letters;
}
private MenuItem getMenuItem(int index) {
return menuItems.get(index);
}
private void setOnKeyPressedEvent() {
this.setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(KeyEvent e) {
if (e.getCode() == KeyCode.UP) {
if (currentItem > 0) {
getMenuItem(currentItem).setActive(false);
getMenuItem(--currentItem).setActive(true);
}
}
if (e.getCode() == KeyCode.DOWN) {
if (currentItem < menuItems.size() - 1) {
getMenuItem(currentItem).setActive(false);
getMenuItem(++currentItem).setActive(true);
}
}
if (e.getCode() == KeyCode.ENTER) {
getMenuItem(currentItem).activate();
}
}
});
}
}
public class MenuItem extends HBox {
static final Font FONT = Font.font("", FontWeight.BOLD, 30);
private Group c1 = createTriCircle();
private Group c2 = createTriCircle();
private Text text;
private Runnable script;
private static Circle createCircle(double centerX, double centerY) {
final double innerRadius = 2;
final double outerRadius = 5;
Circle circle = new Circle(centerX, centerY, (innerRadius + outerRadius) / 2, null);
circle.setStroke(Color.WHITE);
circle.setStrokeWidth(outerRadius - innerRadius);
return circle;
}
private static Group createTriCircle() {
return new Group(
createCircle(0, 0),
createCircle(5, 0),
createCircle(2.5, -5));
}
public MenuItem(String name) {
super(15);
setAlignment(Pos.CENTER);
text = new Text(name);
text.setFont(FONT);
setEffect(new GaussianBlur(2));
getChildren().addAll(c1, text, c2);
setActive(false);
setOnActivate(() -> System.out.println(name + " activated"));
}
public void setActive(boolean b) {
c1.setVisible(b);
c2.setVisible(b);
text.setFill(b ? Color.WHITE : Color.GREY);
}
public void setOnActivate(Runnable r) {
script = r;
}
public void activate() {
if (script != null) {
script.run();
}
}
}
To adjust the distance between title and menu items, you could use VBox.setMargin.

javafx multiple buttons to same handler

I try to make a simple calculator with 20 buttons and one handler. In java I can use 'if' statement with event.getSource() in ActionPerformed to check which button is pressed, but it doesn't work with handler in javafx. Is it possible in javafx that all buttons has one handler? (I don't want to use java 8 Lambdas.)
Last time I tried with setId/getId but it same not work (to me).
public class Calculator extends Application {
public Button b0, b1;
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
b0 = new Button("0");
b0.setId("0");
b0.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid.add(b0, 0, 1);
b0.setOnAction(myHandler);
b1 = new Button("1");
b1.setId("1");
b1.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid.add(b1, 0, 0);
b1.setOnAction(myHandler);
Scene scene = new Scene(grid, 365, 300);
scene.getStylesheets().add
(Calculator.class.getResource("calculator.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
final EventHandler<ActionEvent> myHandler = new EventHandler<ActionEvent>(){
#Override
public void handle(final ActionEvent event) {
Button x = (Button) event.getSource();
if (x.getId().equals(b0.getId()))
System.out.println("0");
else if(x.getId().equals(b1.getId()))
System.out.println("1");
}
};
public static void main(String[] args) {
launch(args);
}
}
I tested your code and it seems to work just fine.
There's no real reason to test the ids of the buttons, though. If you really want to use the same handler (which I don't advise), just test for equality between each button and the source of the event:
final EventHandler<ActionEvent> myHandler = new EventHandler<ActionEvent>(){
#Override
public void handle(final ActionEvent event) {
if (event.getSource() == b0)
System.out.println("0");
else if(event.getSource() == b1)
System.out.println("1");
}
};
But it's (almost?) always better to use a different handler for each action. It keeps the code free of all the if/else constructs, which both makes it cleaner and better in terms of performance. Here, since your buttons do almost the same thing, you can use a single implementation but multiple objects.
Here's a complete example:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Calculator extends Application {
private final IntegerProperty value = new SimpleIntegerProperty();
class NumberButtonHandler implements EventHandler<ActionEvent> {
private final int number ;
NumberButtonHandler(int number) {
this.number = number ;
}
#Override
public void handle(ActionEvent event) {
value.set(value.get() * 10 + number);
}
}
#Override
public void start(Stage primaryStage) {
GridPane grid = createGrid();
for (int n = 1; n<10; n++) {
Button button = createNumberButton(n);
int row = (n-1) / 3;
int col = (n-1) % 3 ;
grid.add(button, col, 2 - row);
}
Button zeroButton = createNumberButton(0);
grid.add(zeroButton, 1, 3);
Button clearButton = createButton("C");
// without lambdas:
// clearButton.setOnAction(
// new EventHandler<ActionEvent>() {
// #Override
// public void handle(ActionEvent event) {
// value.set(0);
// }
// }
// );
// with lambdas:
clearButton.setOnAction(event -> value.set(0));
grid.add(clearButton, 2, 3);
TextField displayField = createDisplayField();
BorderPane root = new BorderPane();
root.setPadding(new Insets(10));
root.setTop(displayField);
root.setCenter(grid);
Scene scene = new Scene(root, 365, 300);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
private Button createNumberButton(int number) {
Button button = createButton(Integer.toString(number));
button.setOnAction(new NumberButtonHandler(number));
return button ;
}
private Button createButton(String text) {
Button button = new Button(text);
button.setMaxSize(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
GridPane.setFillHeight(button, true);
GridPane.setFillWidth(button, true);
GridPane.setHgrow(button, Priority.ALWAYS);
GridPane.setVgrow(button, Priority.ALWAYS);
return button ;
}
private GridPane createGrid() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(5);
grid.setVgap(5);
grid.setPadding(new Insets(10));
return grid;
}
private TextField createDisplayField() {
TextField displayField = new TextField();
displayField.textProperty().bind(Bindings.format("%d", value));
displayField.setEditable(false);
displayField.setAlignment(Pos.CENTER_RIGHT);
return displayField;
}
public static void main(String[] args) {
launch(args);
}
}

Resources