How do I specify a material for an fxyz3d shape? When I add to a JavaFX Group of 3D objects the fxyz3d node
Cone cone = new Cone(coneFacets, coneRadius, coneHeight);
cone.setMaterial(Materials.redMaterial());
it turns every shape in that group solid black, not just the cone, regardless of what any of the specified materials are. If I comment out the above two lines and the one that adds the cone to the group, all the displays of the other shapes return to their specified appearances.
I am using javafx-sdk-17.0.1, fxyz3d-0.5.4.jar, JavaSE-16, Windows 10. Is Javadoc available for fxyz3d anywhere? Or is it necessary to download source and build it locally?
The redMaterial is defined as
final PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.INDIANRED);
material.setSpecularColor(Color.RED);
The following code will reproduce this. As is, both cone and cylinder display black. Comment out the four lines that create and add the Cone, and the cylinder will display red as specified by the material. (Don't otherwise use this as a starting-point example, as there are also issues with automatic scaling as the user adjusts the stage window size yet to be addressed.)
package org.javafxtests;
import org.fxyz3d.shapes.Cone;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.stage.Stage;
public class JxyzConeMaterials extends Application {
// https://www.tutorialspoint.com/javafx/index.htm
// https://www.javatpoint.com/javafx-tutorial
// https://openjfx.io/javadoc/11/
/**
* The application initialization method.
*/
#Override
public void init() throws Exception {
super.init();
}
/**
* Main entry point for all JavaFX applications. The start method is called
* after the init method has returned and the JavaFX framework and hosting
* system are ready to start the application.
*/
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Test JXzy Cone materials");
double sceneWidth = 750.0d;
double sceneHeight = 500.0d;
// The scene structure is constructed from the inside-out (bottom-up).
// A tool bar goes along the top
final FlowPane toolbar = new FlowPane();
toolbar.setPrefWidth(Double.MAX_VALUE);
toolbar.getChildren().addAll(new Label("Files"));
// A TreeView goes down the left side
final TreeView<String> treeView = new TreeView<String>();
treeView.setPrefHeight(Double.MAX_VALUE);
TreeItem<String> treeRoot = new TreeItem<String>("<empty>");
treeView.setRoot(treeRoot);
// A SubScene for viewing 3D objects goes to the right of the TreeView
final SubScene canvasScene = new SubScene(new AnchorPane(), 0, 0);
final AnchorPane canvasRootPane = (AnchorPane) canvasScene.getRoot();
canvasRootPane.setPrefWidth(Double.MAX_VALUE);
canvasRootPane.setPrefHeight(Double.MAX_VALUE);
canvasScene.setWidth(0.75 * sceneWidth); // No setPref methods
canvasScene.setHeight(sceneHeight);
// Create a controllable camera for the 3D SubScene
final PerspectiveCamera canvasCamera = new PerspectiveCamera(true);
final Group cameraTruck = new Group();
final Group cameraGimbal = new Group();
canvasCamera.setFarClip(6000);
canvasCamera.setNearClip(0.01);
cameraGimbal.getChildren().add(canvasCamera);
cameraTruck.getChildren().add(cameraGimbal);
cameraTruck.setTranslateZ(-500.0d);
canvasScene.setCamera(canvasCamera);
canvasRootPane.getChildren().add(cameraTruck);
// Create an HBox at the bottom of the scene,
// TreeView on the left and 3D canvas on the right.
HBox treeAnd3dViews = new HBox(treeView, canvasScene);
treeAnd3dViews.setFillHeight(true);
HBox.setHgrow(canvasScene, Priority.ALWAYS);
treeAnd3dViews.setMaxHeight(Double.MAX_VALUE);
treeAnd3dViews.setMaxWidth(Double.MAX_VALUE);
// Create a VBox to stack the tool bar over the above.
VBox toolbarOverViews = new VBox(toolbar, treeAnd3dViews);
toolbarOverViews.setMaxWidth(Double.MAX_VALUE);
toolbarOverViews.setMaxHeight(Double.MAX_VALUE);
VBox.setVgrow(treeAnd3dViews, Priority.ALWAYS);
AnchorPane.setTopAnchor(toolbarOverViews, 0.0);
AnchorPane.setBottomAnchor(toolbarOverViews, 0.0);
AnchorPane.setLeftAnchor(toolbarOverViews, 0.0);
AnchorPane.setRightAnchor(toolbarOverViews, 0.0);
final Scene scene = new Scene(new AnchorPane(), sceneWidth, sceneHeight);
final AnchorPane sceneRootPane = (AnchorPane) scene.getRoot();
sceneRootPane.getChildren().add(toolbarOverViews);
// Draw an arrow consisting of a cylinder with a cone on top.
double lineRadius = 1.0d;
double lineLength = 25.0d;
int coneFacets = 6;
double coneRadius = 3.0d;
double coneHeight = 6.0d;
final PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.INDIANRED);
material.setSpecularColor(Color.RED);
Cylinder cylinder = new Cylinder(lineRadius, lineLength);
cylinder.setMaterial(material);
Cone cone = new Cone(coneFacets, coneRadius, coneHeight);
cone.setMaterial(material);
// The cone points in the negative Y direction
cone.setTranslateY(-(lineLength / 2.0d) - coneHeight );
canvasRootPane.getChildren().add(cylinder);
canvasRootPane.getChildren().add(cone);
// Show
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() throws Exception {
super.stop();
}
/**
* Main method to launch the application with parameters if needed.
* This may or may not be called, depending on how this application
* is launched.
*
* #param args specifies arguments to {#linkplain Application#launch)}.
*/
public static void main(String[] args) {
launch(args);
}
}
Related
I'm writing a rudimentary Candlestick chart class in which the candlesticks are created as Regions and are plotted by setting their layoutX and layoutY values to the getDisplayPosition() of the relevant axis.
For example, to plot a candlestick at value 3 on an X axis, I do this:
candlestick.setLayoutX(xAxis.getDisplayPosition(3));
When the stage resizes or when the axes are zoomed in or out, the candlesticks' layout values have to be reset so that the chart renders correctly. I'm currently handling this via ChangeListeners for resize events and Button.setOnAction()s for zooming.
However, I'd rather bind the candlesticks' layout properties to the axes' display positions than set/reset the layout values, but can't find a "displayPositionProperty" (or similar) for a NumberAxis.
Is it possible to do this? Which NumberAxis property would I bind to? ie.
candlestick.layoutXProperty().bind(xAxis.WHICH_PROPERTY?);
Also, would binding the properties be more efficient than resetting layout positions? Some of the charts could potentially have thousands of candlesticks but I can't test resource usage until I figure out how to code the bind.
I've experimented with scaling the candlesticks to the axes' scale but can't use that approach because scaling a Region affects its border width. For certain types of candlesticks, that can change its meaning.
I've also played with the Ensemble candlestick demo chart. It was useful in giving me a start but is too simplistic for my needs.
Here's a MVCE that demonstrates my approach. Any guidance re binding would be very much appreciated.
I'm using OpenJFX 17.
package test023;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Test023 extends Application {
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
Pane pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane bp = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: black;");
pChart.getChildren().add(point);
//Plot the point in its initial position (value 3 on the axis)
double pointXValue = 3D;
plotPoint(point, pointXValue, xAxis);
//*****************************************************************
//Can the listeners and button.setOnActions be replaced by binding
//the point's layout value to the axis display position?
//*****************************************************************
//Handle resize events
pChart.widthProperty().addListener((obs, oldVal, newVal) -> {
plotPoint(point, pointXValue, xAxis);
});
stage.maximizedProperty().addListener((obs, oldVal, newVal) -> {
plotPoint(point, pointXValue, xAxis);
});
//Handle zooming (hard-coded upper and lower bounds for the
//sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
xAxis.layout();
plotPoint(point, pointXValue, xAxis);
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
xAxis.layout();
plotPoint(point, pointXValue, xAxis);
});
bp.setCenter(vb);
bp.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(bp));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
private void plotPoint(Region region, double axisPos, NumberAxis axis) {
Platform.runLater(() -> {
double posX = axis.getDisplayPosition(axisPos);
region.setLayoutX(posX);
region.setLayoutY(80D);
});
}
public static void main(String args[]){
launch(args);
}
}
+1 for #LukasOwen answer which answer you actual question related to bindings.
But as you are aware that every problem has more than one approach, I am suggesting mine, considering the scalability (adding many points) and too many bindings (for every point).
The key things in this approach are:
You add all your points numbers and its node to a map.
Every time the xAxis is rendered, you update the all the points position. So this will be implicitly done if you resize, change range, or maximize the window.
Below is the example of the approach:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
public class Test023 extends Application {
Map<Double, Region> plotPoints = new HashMap<>();
double yOffset = 80D;
Pane pChart;
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
xAxis.needsLayoutProperty().addListener((obs, old, needsLayout) -> {
if(!needsLayout) {
plotPoints.forEach((num, point) -> {
double posX = xAxis.getDisplayPosition(num);
point.setLayoutX(posX);
point.setLayoutY(yOffset);
});
}
});
pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane root = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
addPoint(3D, "black");
addPoint(4D, "red");
addPoint(5D, "blue");
//Handle zooming (hard-coded upper and lower bounds for the sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
});
root.setCenter(vb);
root.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(root));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
private void addPoint(double num, String color) {
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: " + color);
plotPoints.put(num, point);
pChart.getChildren().add(point);
}
public static void main(String args[]) {
launch(args);
}
}
Something like this would work
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
Pane pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane bp = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: black;");
pChart.getChildren().add(point);
//Plot the point in its initial position (value 3 on the axis)
double pointXValue = 3D;
point.setLayoutY(80D);
point.layoutXProperty().bind(Bindings.createDoubleBinding(()-> {
return xAxis.getDisplayPosition(pointXValue);
}, xAxis.lowerBoundProperty(), xAxis.upperBoundProperty(), pChart.widthProperty()));
//Handle zooming (hard-coded upper and lower bounds for the
//sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
xAxis.layout();
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
xAxis.layout();
});
bp.setCenter(vb);
bp.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(bp));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
This creates a custom double binding with a function that calculates the value of the binding every time the dependencies are changed, see createDoubleBinding​ for more info.
I'm having a problem positioning JavaFX's HBox in a similar manner to Circle.
If using a circle shape it is possible to manually position it such that it is bound to a different node. This is what I've done until now, by having a Pane as the point of reference:
Pane node; //can be dragged around/resized
//...
Circle terminal = new Circle(10);
terminal.setStroke(Color.GREEN);
terminal.setFill(Color.GREEN);
terminal.centerXProperty().bind( node.layoutXProperty() );
terminal.centerYProperty().bind( node.layoutYProperty() );
The pane (node) functions as a graph node and can be dragged around and resized. The circle functions as a port/terminal for edge connections in the graph. Seeing that the node should have more than one the idea is to put the circles into an HBox that is attached/bound to the pane like the circle has until now. This makes it so that manual layout calculations are unnecessary when adding or removing ports, resizing the node, etc. So the code then used was:
Pane node; //can be dragged around/resized
//...
HBox terminalContainer = new HBox();
terminalContainer.layoutXProperty().bind( node.layoutXProperty() );
terminalContainer.layoutYProperty().bind( node.layoutYProperty() );
//... adding circles into HBox as scenegraph children
The only difference is swapping out the HBox for the Circle and using the layoutXProperty() as there is no centerXProperty(). But of course this fails, and the ports appear glued on to the top part of the containing frame, acting strangely. Is there a fix for this? I tried changing the parenting Pane to an anchorPane, this allowed to manually anchor down the HBox in the correct place, but caused issues with the resizing/dragging code.
Minimal example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main2 extends Application {
private AnchorPane component;
#Override
public void start(Stage primaryStage) {
component = new AnchorPane();
Scene scene = new Scene(component, 1024, 768);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
//This works, but is hard to maintain
Cell c1 = new Cell();
Cell c2 = new Cell();
Port p1 = new Port(c1);
Port p2 = new Port(c2);
component.getChildren().addAll(c1, c2, p1, p2);
c1.relocate(150, 150);
c2.relocate(550, 550);
//This does not work, even if unbinding circles, but is simpler
HBox pc1 = new HBox();
HBox pc2 = new HBox();
pc1.layoutXProperty().bind( c1.layoutXProperty() );
pc1.layoutYProperty().bind( c1.layoutYProperty() );
pc2.layoutXProperty().bind( c2.layoutXProperty() );
pc2.layoutYProperty().bind( c2.layoutYProperty() );
Port p3 = new Port(c1);
Port p4 = new Port(c2);
pc1.getChildren().add(p3);
pc2.getChildren().add(p4);
component.getChildren().addAll(pc1, pc2);
}
class Cell extends Pane {
public Cell() {
Rectangle view = new Rectangle(50,50);
view.setStroke(Color.DODGERBLUE);
view.setFill(Color.DODGERBLUE);
getChildren().add(view);
}
}
class Port extends Pane {
public Port(Cell owner) {
Circle view = new Circle(10);
view.setStroke(Color.GREEN);
view.setFill(Color.GREEN);
view.centerXProperty().bind( owner.layoutXProperty() );
view.centerYProperty().bind( owner.layoutYProperty() );
getChildren().add(view);
}
}
public static void main(String[] args) {
launch(args);
}
}
Got it to work, was a typo in the code binding the layoutXProperty twice instead of the layoutYProperty facepalm
I am trying to simulate the effect one would get from this css example:
border-radius: 50%;
From searching the API and reading posts on forums including this one, I found that I should be using -fx-background-radius. This however is not giving me the wanted effect.
I setup a picture as the background using -fx-background-image:url(...) and then I want to make it into a circle.
How can I achieve this?
Update
So I see that I was not being too specific so let me try to elaborate:
I created a Pane object, that does extend the Region class from JavaFX.
main.fxml:
...
<Pane styleClass="wrapper">
<Pane layoutX="34.0" layoutY="28.0" styleClass="image" />
</Pane>
For this pane I created the styleclass image as seen above.
main.css:
.list-contact .image {
-fx-alignment:left;
-fx-pref-height:40px;
-fx-pref-width:40px;
-fx-background-radius:50%;
-fx-background-image:url(...);
-fx-background-repeat:no-repeat;
}
The effect I get:
The effect I want:
I hope this explains it better.
This is not possible from CSS alone, since ImageView does not support any of Region's CSS properties.
However you can use a Ellipse as clip for the ImageView:
#Override
public void start(Stage primaryStage) throws MalformedURLException {
Image img = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/Space_Needle_2011-07-04.jpg/304px-Space_Needle_2011-07-04.jpg");
ImageView iv = new ImageView(img);
Ellipse ellipse = new Ellipse(img.getWidth() / 2, img.getHeight() / 2, img.getWidth() / 2, img.getHeight() / 2);
iv.setClip(ellipse);
Text text = new Text("Space Needle, Seattle, Washington, USA");
StackPane.setAlignment(text, Pos.TOP_CENTER);
StackPane root = new StackPane(text, iv);
Scene scene = new Scene(root, img.getWidth(), img.getHeight());
scene.setFill(Color.AQUAMARINE);
primaryStage.setScene(scene);
primaryStage.show();
}
I know it doesn't look good to let the image cover the text. This is only done for the purpose of demonstration.
It looks like a CSS border-radius: 50% should create an elliptical border, and JavaFX CSS does support the % shorthand for either -fx-border-radius or -fx-background-radius. To get the desired effect, however, use Path.subtract() to create an elliptical matte for the image, as shown below.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
/**
* #see http://stackoverflow.com/a/38008678/230513
*/
public class Test extends Application {
private final Image IMAGE = new Image("http://i.imgur.com/kxXhIH1.jpg");
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Test");
int w = (int) (IMAGE.getWidth());
int h = (int) (IMAGE.getHeight());
ImageView view = new ImageView(IMAGE);
Rectangle r = new Rectangle(w, h);
Ellipse e = new Ellipse(w / 2, h / 2, w / 2, h / 2);
Shape matte = Path.subtract(r, e);
matte.setFill(Color.SIENNA);
StackPane root = new StackPane();
root.getChildren().addAll(view, matte);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In one Line with Circle as Clip.You can use setClip(any shape).:
imageView.setClip(new Circle(width,height,radius);
The width,height,radius have to be slighty smaller that ImageView size to work.
Inspired by GuiGarage web site.
I am working through some coursework and am running into an odd issue. I'm working with javafx learning how to build shapes and work with alignment. Anyway my circle object will not respond to setCenterX or setCenterY commands (the radius definition statement does work) in the original definition statements nor in the commands issued by my event handlers which should be redefining these set x and set y values. I cannot figure out why. Please see my code below. When working correctly my code would allow me to move the circle object around the screen with the buttons and event handlers I've created. If I can figure out why the setCenterX and setCenterY don't work, I'm sure I can get the rest. Thanks for your help in advance.
package bravo15;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.scene.Scene;
public class FifteenDotThreeVersionThree extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
Circle circle = new Circle();
circle.setCenterX(300);
circle.setCenterY(300);
circle.setRadius(50);
// Hold four buttons in an HBox
// Define hbox
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.CENTER);
// define buttons
Button btLeft = new Button("Left");
Button btRight = new Button("Right");
Button btUp = new Button("Up");
Button btDown = new Button("Down");
// add defined buttons into the hbox
hBox.getChildren().add(btLeft);
hBox.getChildren().add(btRight);
hBox.getChildren().add(btUp);
hBox.getChildren().add(btDown);
// Create and register the handlers for the four buttons
btLeft.setOnAction(e -> circle.setCenterX(circle.getCenterX() - 10));
btRight.setOnAction(e -> circle.setCenterX(circle.getCenterX() + 10));
btUp.setOnAction(e -> circle.setCenterY(circle.getCenterY() + 10));
btDown.setOnAction(e -> circle.setCenterY(circle.getCenterY() - 10));
BorderPane borderPane = new BorderPane();
borderPane.setTop(circle);
borderPane.setBottom(hBox);
BorderPane.setAlignment(hBox, Pos.CENTER);
// Create a scene and place it in the stage
Scene scene = new Scene(borderPane, 200, 200);
primaryStage.setTitle("ControlCircle Version 3"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
A BorderPane manages the layout of its components, so it positions the circle for you by setting its layoutX and layoutY properties so that it appears at the top left.
Wrap it in a Pane, which performs no layout, and place the Pane in the top of the border pane:
borderPane.setTop(new Pane(circle));
Note that you have things set up so that it is initially off-screen. You probably want to increase the size of the scene:
Scene scene = new Scene(borderPane, 600, 600);
You can do it this way to shift the circle:
btLeft.setOnAction(e -> circle.setTranslateX(circle.getTranslateX() - 10));
btRight.setOnAction(e -> circle.setTranslateX(circle.getTranslateX() + 10));
btUp.setOnAction(e -> circle.setTranslateY(circle.getTranslateY() - 10));
btDown.setOnAction(e -> circle.setTranslateY(circle.getTranslateY() + 10));
setTranslateX():
Defines the x coordinate of the translation that is added to this
Node's transform. The node's final translation will be computed as
layoutX + translateX, where layoutX establishes the node's stable
position and translateX optionally makes dynamic adjustments to that
position. This variable can be used to alter the location of a node
without disturbing its layoutBounds, which makes it useful for
animating a node's location.
And it looks better with borderPane.setCenter(circle); than borderPane.setTop(circle);.
I have also removed the following lines:
circle.setCenterX(300);
circle.setCenterY(300);
I have been trying to learn more on using JavaFX and in this program I am trying to display a 3 by 3 game of tic tac toe that has already been played. I have created my ImageViews and set the images I want to use but once I started plugging them into columns and rows I noticed I cannot use the same one twice. I have an image for an empty space, an X, and an O. Once I use one more than once I get an "Exception while running application". Might be a rookie mistake, but an explanation would be greatly appreciated.
package Fresh;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Fresh extends Application {
#Override
public void start(Stage primaryStage) {
//Create a pane and set its properties
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
pane.setHgap(5.5);
pane.setVgap(5.5);
//imv0 = X image
final ImageView imv0 = new ImageView();
final Image image0 = new Image(Fresh.class.getResourceAsStream("images/x.gif"));
imv0.setImage(image0);
//imv1 = O image
final ImageView imv1 = new ImageView();
final Image image1 = new Image(Fresh.class.getResourceAsStream("images/o.gif"));
imv1.setImage(image1);
//imv2 = empty image
final ImageView imv2 = new ImageView();
final Image image2 = new Image(Fresh.class.getResourceAsStream("images/empty.gif"));
imv2.setImage(image2);
//Place nodes in the pane
pane.add((imv0),0,0);
pane.add((imv1), 1, 0);
//Once I try to use imv0 again "I get an exception while running".
pane.add((imv0),0,1);
//Create a scene and place it in the stage
Scene scene = new Scene(pane);
primaryStage.setTitle("ShowGridPane");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can use the same Image as many times as you like; however, you can only place a given ImageView in one place in the scene graph. From the Javadocs:
A node may occur at most once anywhere in the scene graph.
To see why this must be true, what would you expect
GridPane.getColumnIndex(imv0)
to return, given the code you have?
So you can do:
final Image image0 = new Image(Fresh.class.getResourceAsStream("images/x.gif"));
ImageView imv00 = new ImageView(image0);
ImageView imv01 = new ImageView(image0);
pane.add(imv00, 0, 0);
pane.add(imv01, 0, 1);
// etc
The overhead here is not too bad; you use the same image data for each ImageView.