How Can I add 3D surface to the view.fxml, there is not "thing" on the Scene Builder panel like surface.
My scene builder hierarchy looks like :
And ss of app - like we can see got something on the left top corner, the surface should be on the middle.
I would like to add first just some samples of 3D surface :
My controller code :
package sample.packet3D;
import org.fxyz.cameras.CameraTransformer;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.fxml.FXML;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.layout.AnchorPane;
public class Window3DController {
#FXML
private AnchorPane anchorPane;
#FXML
private Group group;
private Window3DBuilder window3dBuilder;
private PerspectiveCamera perspectiveCamera;
#FXML
public void initialize() {
perspectiveCamera = new PerspectiveCamera(true);
window3dBuilder = new Window3DBuilder( group, perspectiveCamera );
window3dBuilder.createScene();
group.sceneProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
group.getScene().setCamera(perspectiveCamera);
group.sceneProperty().removeListener(this);
}
});
}
}
Logic class :
package sample.packet3D;
import org.fxyz.cameras.CameraTransformer;
import org.fxyz.shapes.primitives.SurfacePlotMesh;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
public class Window3DBuilder {
private Group group;
private SurfacePlotMesh surface;
private PerspectiveCamera perspectiveCamera;
private CameraTransformer cameraTransformer;
private PointLight light;
public Window3DBuilder( Group group, PerspectiveCamera perspectiveCamera ) {
this.group = group;
this.perspectiveCamera = perspectiveCamera;
cameraTransformer = new CameraTransformer();
}
public void createScene() {
createSurface();
createLight();
group.getChildren().addAll(surface);
cameraTransformer.setTranslate(0, 0, 0);
cameraTransformer.getChildren().addAll(perspectiveCamera);
perspectiveCamera.setNearClip(0.1);
perspectiveCamera.setFarClip(100000.0);
perspectiveCamera.setTranslateX((group.getBoundsInLocal().getMaxX() + group.getBoundsInLocal().getMinX()) / 2d);
perspectiveCamera.setTranslateY((group.getBoundsInLocal().getMaxY() + group.getBoundsInLocal().getMinY()) / 2d);
double max = Math.max(group.getBoundsInLocal().getWidth(), group.getBoundsInLocal().getHeight());
perspectiveCamera.setTranslateZ(-2 * max);
}
public void createLight() {
light = new PointLight(Color.WHITE);
cameraTransformer.getChildren().add(light);
light.setTranslateX(perspectiveCamera.getTranslateX());
light.setTranslateY(perspectiveCamera.getTranslateY());
light.setTranslateZ(perspectiveCamera.getTranslateZ());
}
private void createSurface() {
surface = new SurfacePlotMesh(
p-> Math.sin(p.magnitude() + 1e-10) / (p.magnitude() + 1e-10),
20, 20, 100, 100, 4);
surface.setCullFace(CullFace.NONE);
surface.setTextureModeVertices3D(1530, p -> p.magnitude());
surface.getTransforms().addAll(new Rotate(200, Rotate.X_AXIS), new Rotate(-20, Rotate.Y_AXIS));
}
}
And view :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.canvas.*?>
<?import javafx.scene.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.packet3D.Window3DController">
<children>
<Group fx:id="group">
<effect>
<Lighting>
<bumpInput>
<Shadow />
</bumpInput>
<light>
<Light.Distant />
</light>
</Lighting>
</effect>
</Group>
<PerspectiveCamera fx:id="perspectiveCamera" visible="false" />
</children>
</AnchorPane>
What Am I doing wrong ? Could someone help me ?
Also this is one of the window, to which I am entering by pressing the button.
#FXML
public void moveTo3DScene(ActionEvent event) throws IOException {
Stage stage3D = (Stage) ((Node) event.getSource()).getScene().getWindow();
Parent parent3D = FXMLLoader.load(getClass().getResource("packet3D/Window3DSceneView.fxml"));
stage3D.setTitle("Animation 3D");
stage3D.setScene(new Scene(parent3D, 1200, 800));
stage3D.show();
}
You have a problem with PerspectiveCamera. It has a boolean parameter called fixedEyeAtCameraZero that by default is false, and a very small surface is shown at the top left corner of your scene.
We need to set it to true, so:
If fixedEyeAtCameraZero is true, the eye position is fixed at (0, 0, 0) in the local coordinates of the camera
But unfortunately you can't set the parameter, there is no setFixedEyeAtCameraZero() method. The only way to change it is with the camera constructor.
This means that you have to remove the PerspectiveCamera from the FXML file, and add it by code on the controller
public class Window3DController {
#FXML
private AnchorPane anchorPane;
#FXML
private Group group;
private Window3DBuilder window3dBuilder;
private PerspectiveCamera perspectiveCamera;
#FXML
public void initialize() {
perspectiveCamera = new PerspectiveCamera(true);
window3dBuilder = new Window3DBuilder(group, perspectiveCamera);
window3dBuilder.createScene();
group.sceneProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
group.getScene().setCamera(perspectiveCamera);
group.sceneProperty().removeListener(this);
}
});
}
}
One last step: you need to set some parameters to the camera, basically its z coordinate based on the size of the mesh:
public void createScene() {
createSurface();
group.getChildren().addAll(surface);
perspectiveCamera.setNearClip(0.1);
perspectiveCamera.setFarClip(100000.0);
perspectiveCamera.setTranslateX((group.getBoundsInLocal().getMaxX() + group.getBoundsInLocal().getMinX()) / 2d);
perspectiveCamera.setTranslateY((group.getBoundsInLocal().getMaxY() + group.getBoundsInLocal().getMinY()) / 2d);
double max = Math.max(group.getBoundsInLocal().getWidth(), group.getBoundsInLocal().getHeight());
perspectiveCamera.setTranslateZ(-2 * max);
}
This will show your surface, but not as you will expect: the effects you are applying are intended for 2D:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Window3DController">
<children>
<Group fx:id="group" />
</children>
</AnchorPane>
Remove those effects, and add them by code using PointLight:
public class Window3DBuilder {
private final Group group;
private SurfacePlotMesh surface;
private final CameraTransformer cameraTransformer;
private final PerspectiveCamera perspectiveCamera;
private PointLight light;
public Window3DBuilder( Group group, PerspectiveCamera perspectiveCamera ) {
this.group = group;
this.perspectiveCamera = perspectiveCamera;
cameraTransformer = new CameraTransformer();
}
public void createScene() {
createSurface();
group.getChildren().addAll(surface, cameraTransformer);
cameraTransformer.setTranslate(0, 0, 0);
cameraTransformer.getChildren().addAll(perspectiveCamera);
perspectiveCamera.setNearClip(0.1);
perspectiveCamera.setFarClip(100000.0);
perspectiveCamera.setTranslateX((group.getBoundsInLocal().getMaxX() + group.getBoundsInLocal().getMinX()) / 2d);
perspectiveCamera.setTranslateY((group.getBoundsInLocal().getMaxY() + group.getBoundsInLocal().getMinY()) / 2d);
double max = Math.max(group.getBoundsInLocal().getWidth(), group.getBoundsInLocal().getHeight());
perspectiveCamera.setTranslateZ(-2 * max);
createLight();
}
public void createLight() {
light = new PointLight(Color.WHITE);
cameraTransformer.getChildren().add(light);
light.setTranslateX(perspectiveCamera.getTranslateX());
light.setTranslateY(perspectiveCamera.getTranslateY());
light.setTranslateZ(perspectiveCamera.getTranslateZ() / 10);
}
private void createSurface() {
surface = new SurfacePlotMesh(
p-> Math.sin(p.magnitude() + 1e-10) / (p.magnitude() + 1e-10),
20, 20, 100, 100, 4);
surface.setCullFace(CullFace.NONE);
surface.setTextureModeVertices3D(1530, p -> p.magnitude());
surface.getTransforms().addAll(new Rotate(200, Rotate.X_AXIS), new Rotate(-20, Rotate.Y_AXIS));
}
}
I wanted to keep the camera in the .fxml file. I created a custom control based on PerspectiveCamera.
Start with a very simple class...
import javafx.scene.PerspectiveCamera;
public class PerspectiveCamera3D extends PerspectiveCamera {
// force 3D
public PerspectiveCamera3D() {
super(true);
}
// toss the parameter, force 3D
public PerspectiveCamera3D(final boolean fixedEyeAtCameraZero) {
this();
}
}
Export to a .jar file.
Launch Scene Builder and open the .fxml file for where you want the camera to be.
Open the 'gear' menu on the Library header.
Then Import FXML/Jar. Import your newly created .jar file. A dialog will pop up with your control listed. Once confirmed, the control will appear in the custom menu. Your control is now ready for use, just like any other one.
The 'Fixed Eye...' check box will still be read-only, but it will be checked. All of the other properties can be set as desired. To set the fx:id just add the following into your controller code...
#FXML
public PerspectiveCamera3D cambot;
Here is a bit more detailed of an example ... https://rterp.wordpress.com/2014/07/28/adding-custom-javafx-component-to-scene-builder-2-0-part-2/
The only problem I've had doing it this way, is when launching Scene Builder by clicking on the .fxml file in Eclipse causes an exception (I believe it is a local problem on my computer due to the way I'm launching things and their working directories). It works fine if I open Scene Builder, and then open the .fxml file from the Scene Builder File menu.
Related
I have a problem with fxml controller communication.
I have App class which is a controller:
package org.example.gui;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import org.example.Game;
import org.example.Main;
import java.io.IOException;
public class App extends Application {
private Game game;
private MapVisualiser mapV;
#FXML
private GridPane mapGridPane;
#Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/main-scene.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 1530, 790);
stage.setResizable(true);
stage.setMaximized(true);
stage.setTitle("GymSnake");
stage.setScene(scene);
this.game = new Game();
this.mapV = new MapVisualiser(game.getMap(),mapGridPane);
stage.show();
}
}
and a fxml file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.GridPane?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.gui.App">
<GridPane fx:id="mapGridPane" alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" nodeOrientation="LEFT_TO_RIGHT">
</GridPane>
</AnchorPane>
Also the MapVisualiser code to make sure everything is working fine
public MapVisualiser(Map map, GridPane gridPane) {
this.map = map;
this.gridPane = gridPane;
calculateColumnsAndRowsSize();
}
private void calculateColumnsAndRowsSize() {
int squareHeight = (int) Math.ceil(1.0 * Configuration.GRID_HEIGHT / map.getN());
int squareWidth = (int) Math.ceil(1.0 * Configuration.GRID_WIDTH / map.getN());
this.sideLengthOfSquarePx = Math.max(40, Math.max(squareWidth, squareHeight));
gridPane.getColumnConstraints().add(new ColumnConstraints(sideLengthOfSquarePx));
gridPane.getRowConstraints().add(new RowConstraints(sideLengthOfSquarePx));
}
I don't understand why my grid pane returns null
Caused by: java.lang.NullPointerException: Cannot invoke "javafx.scene.layout.GridPane.getColumnConstraints()" because "this.gridPane" is null
Please help
I am using TitledPanes ScrollPanes and TableViews and I have the problem, when I collapse a titledPane, the horizontal ScrollBar of the TableView resets.
Here is a code example where you can verify it:
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.AnchorPane;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable {
#FXML
private AnchorPane content;
#FXML
private TitledPane titledPane;
#FXML
private TableView<Object> tableView;
#Override
public void initialize(URL location, ResourceBundle resources) {
titledPane.prefHeightProperty().bind(content.heightProperty());
tableView.prefWidthProperty().bind(content.widthProperty());
tableView.getColumns().forEach(col -> col.setPrefWidth(300)); // to have enough "space" to scroll
tableView.setItems(FXCollections.observableArrayList(new Object()));
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="stackoverflow.testscroll.Controller"
fx:id="content">
<TitledPane fx:id="titledPane">
<TableView fx:id="tableView">
<columns>
<TableColumn/>
<TableColumn/>
<TableColumn/>
<TableColumn/>
<TableColumn/>
<TableColumn/>
<TableColumn/>
<TableColumn/>
</columns>
</TableView>
</TitledPane>
</AnchorPane>
Any idea how can I prevent the scroll of the tableview to reset every time I collapse the pane?
After a bit of digging, it looks like some layout optimization in VirtualFlow might be the reason (all seems to be fine if the scrolled content is not a TableView - not thoroughly analyzed, though)
What happens is:
during collapse, the TitledPane's content is resized vertically to 0
in VirtualFlow's layoutChildren a zero height/width is special cased to do nothing except hide everything, including the scrollBars
an internal listener to the scrollBar's visiblilty resets its value to 0
A tentative (read: dirty and might have unwanted side-effects, totally untested beyond this quick outline!) hack around is a custom TableViewSkin that tries to "remember" the last not-zero value and resets it on getting visible again.
An example:
public class TitledPaneTableScroll extends Application {
public static class TableViewScrollSkin<T> extends TableViewSkin<T> {
DoubleProperty hvalue = new SimpleDoubleProperty();
public TableViewScrollSkin(TableView<T> control) {
super(control);
installHBarTweak();
}
private void installHBarTweak() {
// Note: flow and bar could be legally retrieved via lookup
// protected api pre-fx9 and post-fx9
VirtualFlow<?> flow = getVirtualFlow();
// access scrollBar via reflection
// this is my personal reflective access utility method - use your own :)
ScrollBar bar = (ScrollBar) FXUtils
.invokeGetFieldValue(VirtualFlow.class, flow, "hbar");
bar.valueProperty().addListener((s, o, n) -> {
if (n.intValue() != 0) {
hvalue.set(n.doubleValue());
// debugging
// new RuntimeException("who is calling? \n").printStackTrace();
}
//LOG.info("hbar value: " + n + "visible? " + bar.isVisible());
});
bar.visibleProperty().addListener((s, o, n) -> {
if (n) {
bar.setValue(hvalue.get());
}
});
}
}
int counter;
private Parent createContent() {
TableView<Object> table = new TableView<>(FXCollections.observableArrayList(new Object()) ) {
#Override
protected Skin<?> createDefaultSkin() {
return new TableViewScrollSkin<>(this);
}
};
table.getColumns().addAll(Stream
.generate(TableColumn::new)
.limit(10)
.map(col -> {
col.setPrefWidth(50);
col.setText("" + counter++);
return col;
})
.collect(Collectors.toList()));
TitledPane titled = new TitledPane("title", table);
titled.setAnimated(true);
BorderPane content = new BorderPane(titled);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent(), 400, 400));
// stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TitledPaneTableScroll.class.getName());
}
I'm not sure how to call this problem but you will definitely understand what I mean...
at least I hope you will.
To start with: I have my Pane with a ScrollPane in it. This Pane also contains two Buttons on which I can create rectangles in my ScrollPane.
I can drag those Rectangles around and they can be moved out of view. This far everything works as it should be except the ScrollPane. Usually you had the ScrollPane expand his Scroll so you can, if you let go of the Rectangle, scroll to that Rectangle and grab it again but nice and friendly JavaFX doesnt know how to expand the Scroll so I cant get to this Rectangle again.
Or I just miss some important part (which is the more likely thing to happen)
Some words to my Code: I don't think that its a problem in my existing code but more a problem that needs a workaround. The only things that I changed for the ScrollPane are that I set the ScrollBarPolicy to always and set the ScrollPane Pannable but this is consumed when I click on a Rectangle so I can move them and don't pan the ScrollPane.
What you see above is one Rectangle in its full size and one which is partly out of the ScrollPane boundaries but the ScrollPane doesn't expand the ScrollBar.
Doesn't matter how far I move that Rectangle away from those boundaries the Scrollbar won't change which is bad for this is the only reason I chose a ScrollPane and also, I think, the only (easiest) pane to achieve this "editor like" view.
Yea so thank you for at least reading this far and like always sorry for my broken English ^^. I hope you can give me a advice on how to fix this problem!
EDIT:
Here a minimal example:
Controller.java
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
public class Controller implements Initializable{
#FXML ScrollPane actionWin;
Rectangle r;
Double orgTranslateX, orgTranslateY, orgSceneX, orgSceneY, newTranslateX, newTranslateY, offsetY, offsetX;
int i = 50;
List<Rectangle> recs = new ArrayList<Rectangle>();
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
actionWin.setPannable(true);
actionWin.setHbarPolicy(ScrollBarPolicy.ALWAYS);
actionWin.setVbarPolicy(ScrollBarPolicy.ALWAYS);
}
#FXML
public void createEntity(){
Pane container = new Pane();
// Button 1
r = new Rectangle();
r.addEventHandler(MouseEvent.ANY, event -> {
if(event.getButton() != MouseButton.MIDDLE) event.consume();
});
r.setOnMousePressed(onMousePressedEventHandler);
r.setOnMouseDragged(onMouseDraggedEventHandler);
r.setX(i);
r.setY(i);
r.setWidth(50);
r.setHeight(20);
i+=30;
recs.add(r);
for(Rectangle rec : recs){
container.getChildren().add(rec);
}
actionWin.setContent(container);
try {
AnchorPane root = (AnchorPane) FXMLLoader.load(getClass().getResource("Main.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent arg0) {
orgSceneX = arg0.getSceneX();
orgSceneY = arg0.getSceneY();
orgTranslateX = ((Rectangle) (arg0.getSource())).getTranslateX();
orgTranslateY = ((Rectangle) (arg0.getSource())).getTranslateY();
}};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent arg0) {
offsetX = arg0.getSceneX() - orgSceneX;
offsetY = arg0.getSceneY() - orgSceneY;
newTranslateX = orgTranslateX + offsetX;
newTranslateY = orgTranslateY + offsetY;
((Rectangle)(arg0.getSource())).setTranslateX(newTranslateX);
((Rectangle)(arg0.getSource())).setTranslateY(newTranslateY);
}};
}
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Spinner?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="root" maxHeight="720.0" maxWidth="1280.0" minHeight="720.0" minWidth="1280.0" prefHeight="720.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<children>
<Button fx:id="createEntity_B" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onAction="#createEntity" text="Create Entity" />
<ScrollPane fx:id="actionWin" layoutX="14.0" layoutY="39.0" prefHeight="672.0" prefWidth="996.0" />
</children>
</AnchorPane>
Main.java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
public void start(Stage primaryStage) {
try {
AnchorPane root = (AnchorPane)
FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Wrap these three classes in a package called "application" and you're good to go.
You're using translateX and translateY to determine the new position of the Rectangles.
Transforms like the translate properties are not taken into account when the parent determines it's size. The child is treated as if it was located at (layoutX, layoutY). You need to modify those properties, if you want the Pane to be resized properly.
Some other things in the createEntity seem odd though:
You recreate the Pane instead of creating it once in the fxml.
You load a fxml that never seems to be used.
Also you should prefer the primitive type double to the reference type Double unless you're not doing any arithmetic operations or comparisons.
<AnchorPane fx:id="root" maxHeight="720.0" maxWidth="1280.0" minHeight="720.0" minWidth="1280.0" prefHeight="720.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<children>
<Button fx:id="createEntity_B" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onAction="#createEntity" text="Create Entity" />
<ScrollPane fx:id="actionWin" layoutX="14.0" layoutY="39.0" prefHeight="672.0" prefWidth="996.0">
<content>
<Pane fx:id="container"/>
</content>
</ScrollPane>
</children>
</AnchorPane>
#FXML
private Pane container;
#FXML
public void createEntity() {
class DraggedHandler implements EventHandler<MouseEvent> {
double offsetX;
double offsetY;
#Override
public void handle(MouseEvent event) {
Node source = (Node) event.getSource();
Point2D pt = source.localToParent(event.getX(), event.getY());
source.setLayoutX(pt.getX() + offsetX);
source.setLayoutY(pt.getY() + offsetY);
}
}
Rectangle rect = new Rectangle(i, i, 50, 20);
i += 30;
container.getChildren().add(rect);
rect.addEventHandler(MouseEvent.ANY, event -> {
if (event.getButton() != MouseButton.MIDDLE) {
event.consume();
}
});
DraggedHandler handler = new DraggedHandler();
rect.setOnMouseDragged(handler);
rect.setOnMousePressed(evt -> {
Node source = (Node) evt.getSource();
Point2D pt = source.localToParent(evt.getX(), evt.getY());
handler.offsetX = source.getLayoutX() - pt.getX();
handler.offsetY = source.getLayoutY() - pt.getY();
});
}
I'm making a paint program but where there is a DropShadow around the drawings the user makes. When they erase, it also erases the DropShadow. If I try to reapply the DropShadow (to get it to surround the drawing's new boundaries), it works, but it makes the rest of the already applied DropShadow darker which I don't want.
The solutions I can think of are two-fold:
Remove DropShadow effect and reapply it on every erase. As far as I know this is not possible while using GraphicsContext.applyEffect()
Apply DropShadow on a separate canvas and clear it and redraw on every erase. The code below implements my embarrassing attempt at this solution.
Here is a picture of what happens currently:
Here is my code
FXMLDocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>
<AnchorPane id="AnchorPane" prefHeight="399.0" prefWidth="465.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="cartographerfx.FXMLDocumentController">
<children>
<Pane fx:id="canvasPane" layoutX="26.0" layoutY="20.0" prefHeight="362.0" prefWidth="417.0" style="-fx-border-style: solid;">
<children>
<Canvas fx:id="canvas" height="362.0" onMouseDragged="#CanvasMouseDragged" onMousePressed="#CanvasMouseDown" onMouseReleased="#CanvasMouseUp" width="417.0" />
</children>
</Pane>
</children>
</AnchorPane>
FXMLDocumentController.java
package cartographerfx;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
/**
*
* #author Dan
*/
public class FXMLDocumentController implements Initializable {
#FXML
Canvas canvas;
Canvas shadowLayer;
#FXML
Pane canvasPane;
private GraphicsContext gc;
private GraphicsContext sgc;
private boolean isDragging;
private double lastX;
private double lastY;
#FXML
public void CanvasMouseDragged(MouseEvent event) {
if (isDragging) {
if (event.getButton() == MouseButton.PRIMARY) {
gc.setStroke(Color.BLUE);
gc.setLineWidth(10);
gc.strokeLine(lastX, lastY, event.getX(), event.getY());
} else if (event.getButton() == MouseButton.SECONDARY) {
gc.clearRect(event.getX(), event.getY(), 10, 10);
}
lastX = event.getX();
lastY = event.getY();
}
}
#FXML
public void CanvasMouseDown(MouseEvent event) {
isDragging = true;
lastX = event.getX();
lastY = event.getY();
if (event.getButton() == MouseButton.MIDDLE)
{
sgc = gc;
sgc.applyEffect(new DropShadow());
}
}
#FXML
public void CanvasMouseUp(MouseEvent event) {
isDragging = false;
}
#Override
public void initialize(URL url, ResourceBundle rb) {
gc = canvas.getGraphicsContext2D();
isDragging = false;
shadowLayer = new Canvas(canvas.getWidth(), canvas.getHeight());
sgc = shadowLayer.getGraphicsContext2D();
canvasPane.getChildren().add(shadowLayer);
shadowLayer.toBack();
}
}
CartographerFX.java
package cartographerfx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
*
* #author Dan
*/
public class CartographerFX extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I don't understand why everybody seems to be using the Canvas when the scene-graph would be much better suited for the task. Why don't you just use an ordinary Pane and a Path. You can then apply the drop shadow effect directly to the path. The advantage of the scene-graph is that you can edit every element individually as often as you want whereas the canvas basically is conceptually just an image which you cannot edit at all once it is drawn.
I'm trying to resize a canvas in Javafx. I am using scene builder and fxml. So far, when the user clicks on the canvas the canvas turns black, and when I resize the screen and click on the canvas only the original size of the canvas turns black (canvas is not being resized). I'm not sure how to solve this. Any ideas or solutions would help alot.
Code:
Controller:
public class MainFXMLController implements Initializable
{
#FXML
private Canvas mainCanvas;
#FXML
public GraphicsContext gc;
public void initGraphics()
{
gc = mainCanvas.getGraphicsContext2D();
}
public void drawClicked(MouseEvent me)
{
gc.clearRect(0, 0, mainCanvas.getWidth(), mainCanvas.getHeight());
gc.setFill(Color.BLACK);
gc.fillRect(0, 0, mainCanvas.getWidth(), mainCanvas.getHeight());
System.out.println("Current mosue position: " + me.getX() + ":" + me.getY());
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
initGraphics();
}
}
Fxml:
<AnchorPane id="AnchorPane" prefHeight="600.0" prefWidth="750.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="app.MainFXMLController">
<children>
<Canvas fx:id="mainCanvas" height="565.0" onMouseClicked="#drawClicked" width="750.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="35.0" />
Main Java file:
public class DrawFx extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("MainFXML.fxml"));
Scene scene = new Scene(root);
stage.setTitle("DrawFx");
stage.getIcons().add(new Image("/icon/icon.png"));
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
First some Javadocs :)
A Canvas node is constructed with a width and height that specifies the size of the image into which the canvas drawing commands are rendered. All drawing operations are clipped to the bounds of that image.
So every time the user resize the window we need to change the width of the canvas and then we need to re-draw the canvas.
Lets start by adding a fx:id to the root layout.
<AnchorPane fx:id="anchorPane" prefHeight="600.0" prefWidth="750.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="app.MainFXMLController">
<children>
<Canvas fx:id="mainCanvas" height="565.0" onMouseClicked="#drawClicked" width="750.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="35.0"/>
</children>
</AnchorPane>
Next step is to add a change listener to the root layout which will set the new height and width to the canvas and then redraw it. We can do it inside the initialize() of the controller.
public class Controller implements Initializable {
#FXML
AnchorPane anchorPane;
#FXML
private Canvas mainCanvas;
#FXML
public GraphicsContext gc;
public void initGraphics() {
gc = mainCanvas.getGraphicsContext2D();
}
public void drawClicked() {
gc.clearRect(0, 0, mainCanvas.getWidth(), mainCanvas.getHeight());
gc.setFill(Color.BLACK);
gc.fillRect(0, 0, mainCanvas.getWidth(), mainCanvas.getHeight());
}
#Override
public void initialize(URL url, ResourceBundle rb) {
initGraphics();
anchorPane.prefWidthProperty().addListener((ov, oldValue, newValue) -> {
mainCanvas.setWidth(newValue.doubleValue());
drawClicked();
});
anchorPane.prefHeightProperty().addListener((ov, oldValue, newValue) -> {
mainCanvas.setHeight(newValue.doubleValue());
drawClicked();
});
}
}
I haven't created a new method for reDraw() since your drawClicked() wasn't doing anything. But, you can separate both the methods once it makes more sense.
The last thing is to bind to root layout's prefWidthProperty() and prefHeightProperty() to the scene's width and height respectively.
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws IOException {
AnchorPane root = FXMLLoader.load(getClass().getResource("MainFXML.fxml"));
Scene scene = new Scene(root);
stage.setTitle("DrawFx");
stage.setScene(scene);
stage.show();
root.prefWidthProperty().bind(scene.widthProperty());
root.prefHeightProperty().bind(scene.heightProperty());
}
}
If you want to resize canvas in fxml and presumably redraw its contents afterwards, the absolute minimum set is something like this:
test.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="test.TestController">
<children>
<Pane fx:id="pane" VBox.vgrow="ALWAYS">
<children>
<Canvas fx:id="canvas" height="${pane.height}" width="${pane.width}"
onWidthChange="#redraw" onHeightChange="#redraw" />
</children>
</Pane>
</children>
</VBox>
TestController.java
package test;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public class TestController {
#FXML
private Canvas canvas;
#FXML
private void redraw() {
double w=canvas.getWidth();
double h=canvas.getHeight();
GraphicsContext gc=canvas.getGraphicsContext2D();
gc.clearRect(0, 0, w, h);
gc.beginPath();
gc.rect(10, 10, w-20, h-20);
gc.stroke();
}
}
Wrapping (it is not part of the functionality, just provided for completeness)
Test.java
package test;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Test extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader=new FXMLLoader(getClass().getResource("test.fxml"));
Parent root=loader.load();
primaryStage.setTitle("Test");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
the test package is there for allowing modular magic,
module-info.java
module cnvtest {
requires transitive javafx.graphics;
requires javafx.fxml;
opens test to javafx.fxml;
exports test;
}
and there are really no more files.