Apply DropShadow as separate layer on JavaFX canvas - java

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.

Related

Label not displaying text even after change (JavaFX)

I'm working on a project and I need to display the information of a Medicine object from a ListView to another Scene.
The user would select a Medicine from the ListView and press the Button to see it's details in the next Scene that would be displayed by Labels. The problem now is, I have transferred the info, and the text property of the Label has changed (observed through println and debugging), but the Label just won't display the changed text.
this is the main
package app;
import app.data.MedicineData;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.w3c.dom.events.Event;
import java.io.IOException;
public class Pharmachine extends Application {
public static final double WIDTH = 480;
public static final double HEIGHT = 720;
#Override
public void start(Stage stage) throws Exception{
Parent homePage = FXMLLoader.load(getClass().getResource("homepage.fxml"));
Scene homePageScene = newDefaultScene(homePage);
stage.setResizable(false);
stage.setScene(homePageScene);
stage.setTitle("Pharmachine");
stage.show();
}
#Override
public void init(){
MedicineData.getInstance().loadData();
}
public static Scene newDefaultScene(Parent parent){
Scene scene = new Scene(parent, WIDTH, HEIGHT);
scene.addEventHandler(MouseEvent.MOUSE_CLICKED,
(mouseEvent) -> {
Node targetNode = scene.getFocusOwner();
if(targetNode != null && targetNode.isFocused()) {
if(targetNode.getParent() != null)
targetNode.getParent().requestFocus();
mouseEvent.consume();
}
}
);
return scene;
}
public static void navigateTo(ActionEvent actionEvent, String filename){
Stage mainWindow = (Stage) ((Node) actionEvent.getSource()).getScene().getWindow();
Scene currentScene = ((Node) actionEvent.getSource()).getScene();
try {
currentScene.setRoot(FXMLLoader.load(Pharmachine.class.getResource(filename)));
mainWindow.setScene(currentScene);
} catch(IOException e){
e.printStackTrace();
}
}
public static void main(String[] args){ launch(args); }
}
this is the fxml for the list's page
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="app.ListpageController" fx:id="pageRoot"
stylesheets="#styles/main.css">
<left>
<GridPane fx:id="listArea" alignment="CENTER">
<ListView fx:id="medicineList"
GridPane.rowIndex="1"/>
</GridPane>
</left>
<center>
<GridPane fx:id="buttonsArea" alignment="CENTER"
hgap="10" vgap="10">
<Button text="Back"
onAction="#displayHomePage"
GridPane.halignment="CENTER"/>
<Button text="Details"
onAction="#displayDetailsPage"
GridPane.rowIndex="1"
GridPane.halignment="CENTER"/>
</GridPane>
</center>
</BorderPane>
here is the controller for the list's page
package app;
import app.data.Medicine;
import app.data.MedicineData;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.util.Callback;
import java.io.IOException;
public class ListpageController {
#FXML
private BorderPane pageRoot;
#FXML
private GridPane listArea;
#FXML
private GridPane buttonsArea;
#FXML
private ListView<Medicine> medicineList;
#FXML public void initialize(){
listArea.setMaxWidth(Pharmachine.WIDTH * 3/5);
medicineList.setMinWidth(listArea.getMaxWidth());
medicineList.setMinHeight(Pharmachine.HEIGHT);
medicineList.setItems(MedicineData.getInstance().getMedicines());
medicineList.setCellFactory(new Callback<>() {
#Override
public ListCell<Medicine> call(ListView<Medicine> medicineListView) {
ListCell<Medicine> listCell = new ListCell<>(){
#Override
public void updateItem(Medicine medicine, boolean empty){
super.updateItem(medicine, empty);
if(!empty){
HBox medName = new HBox(new Label(medicine.getName()));
HBox.setHgrow(medName, Priority.ALWAYS);
HBox medPrice = new HBox(new Label(String.format("RM%.2f", medicine.getPrice())));
HBox medLabel = new HBox(medName, medPrice);
setGraphic(medLabel);
}
}
};
listCell.setOnMouseClicked(
(mouseEvent) -> {
if(mouseEvent.getClickCount() == 2){
medicineList.getSelectionModel().clearSelection();
}
}
);
return listCell;
}
});
}
#FXML private void displayHomePage(ActionEvent actionEvent){
Pharmachine.navigateTo(actionEvent, "homepage.fxml");
}
#FXML private void displayDetailsPage(ActionEvent actionEvent){
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("detailspage.fxml"));
try {
loader.load();
} catch(IOException e){
e.printStackTrace();
}
DetailspageController controller = loader.getController();
Medicine selectedMedicine = medicineList.getSelectionModel().getSelectedItem();
if(selectedMedicine != null) {
controller.setInfo(selectedMedicine);
Pharmachine.navigateTo(actionEvent, "detailspage.fxml");
}
}
}
this is the fxml for the next scene (just simple for now)
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.text.Text?>
<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:id="pageRoot" fx:controller="app.DetailspageController"
stylesheets="#styles/main.css">
<center>
<GridPane fx:id="detailsArea"
alignment="CENTER" hgap="10" vgap="10">
<Label text="Details"/>
<Text text="Name: "
GridPane.rowIndex="1"/>
<Label fx:id="medNameLabel" text="~"
GridPane.rowIndex="1" GridPane.columnIndex="1"/>
<Text text="Price: "
GridPane.rowIndex="2"/>
<Label fx:id="medPriceLabel" text="~"
GridPane.rowIndex="2" GridPane.columnIndex="1"/>
</GridPane>
</center>
</BorderPane>
and this is the scene's controller
package app;
import app.data.Medicine;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import java.net.URL;
import java.util.ResourceBundle;
public class DetailspageController implements Initializable {
private Medicine selectedMedicine;
#FXML
private BorderPane pageRoot;
#FXML
private GridPane detailsArea;
#FXML
private Label medNameLabel;
#FXML
private Label medPriceLabel;
#Override
public void initialize(URL url, ResourceBundle rb){
}
public void setInfo(Medicine medicine){
selectedMedicine = medicine;
medNameLabel.setText(selectedMedicine.getName());
medPriceLabel.setText(String.format("RM%.2f", selectedMedicine.getPrice()));
System.out.println(medNameLabel.textProperty());
System.out.println(medNameLabel.getText());
medNameLabel.setStyle("-fx-background-color: red;");
}
}
The help would mean a lot. Thank you :]
In your displayDetailsPage method, you load a scene from detailspage.fxml, and you update its labels by calling the setInfo method of the controller.
Then, you call Pharmachine.navigateTo, which loads detailspage.xml again and replaces the scene root with the newly loaded root. You updated the text of the labels in the first details page, but the second details page is brand new, so it doesn’t have those changes.
The second argument in the navigateTo method should be of type Parent rather than a String. navigateTo should not attempt to load any .fxml file; let that be the caller’s responsibility.
Side note: When a list has multiple attributes for each data item, you should probably use a TableView rather than a ListView.

How do I add a Splash screen to a main program in javafx?

I have 2 files, Splash.java and Main.java. Here are the 2 codes:
Splash.java
package application;
import javafx.animation.*;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.Font;
/**
* This is my own splash screen, that I made myself.
*
*/
public class Splash{
static Stage splash;
static Rectangle rect = new Rectangle();
public Splash() {
splash = new Stage(StageStyle.UNDECORATED);
splash.toFront();
splash.setHeight(200);
splash.setWidth(400);
}
public Splash show() {
/*
* Part 1:
* This is the rolling square animation.
* This animation looks cool for a loading screen,
* so I made this. Only the lines of code for fading
* from Stack Overflow.
*/
//rectangle insertion
int scale = 30;
int dur = 800;
rect = new Rectangle(100-2*scale,20,scale,scale);
rect.setFill(Color.AQUAMARINE);
//actual animations
//initialising the sequentialTranslation
SequentialTransition seqT = new SequentialTransition(rect);
//umm, ignore this, just some configs that work magic
int[] rotins = {scale,2*scale,3*scale,4*scale,5*scale,-6*scale,-5*scale,-4*scale,-3*scale,-2*scale};
int x,y;
for (int i:rotins) {
//rotating the square
RotateTransition rt = new RotateTransition(Duration.millis(dur),rect);
rt.setByAngle(i/Math.abs(i)*90);
rt.setCycleCount(1);
//moving the square horizontally
TranslateTransition pt = new TranslateTransition(Duration.millis(dur), rect);
x=(int) (rect.getX()+Math.abs(i));
y=(int) (rect.getX()+Math.abs(i)+(Math.abs(i)/i)*scale);
pt.setFromX(x);
pt.setToX(y);
//parallelly execute them and you get a rolling square
ParallelTransition pat = new ParallelTransition();
pat.getChildren().addAll(pt,rt);
pat.setCycleCount(1);
seqT.getChildren().add(pat);
}
//playing the animation
seqT.play();
//lambda code sourced from StackOverflow, fades away stage
seqT.setOnFinished(e->{
Timeline timeline = new Timeline();
KeyFrame key = new KeyFrame(Duration.millis(800),
new KeyValue (splash.getScene().getRoot().opacityProperty(), 0));
timeline.getKeyFrames().add(key);
timeline.setOnFinished((ae) -> System.exit(1));
timeline.play();
});
//The text part
Label label = new Label("Flight");
label.setFont(new Font("Verdana",40));
label.setStyle("-fx-text-fill:white");
label.setLayoutX(140);
label.setLayoutY(70);
Label lab = new Label("Launching...");
lab.setFont(new Font("Times New Roman",10));
lab.setStyle("-fx-text-fill:white");
lab.setLayoutX(170);
lab.setLayoutY(180);
//A complimentary image
Image image = new Image(getClass().getResourceAsStream("launchplane.png"));
ImageView iv = new ImageView(image);
iv.setFitWidth(32);
iv.setFitHeight(32);
iv.setX(174);
iv.setY(130);
//now adding everything to position, opening the stage, start the animation
Pane pane = new Pane(rect,label,lab,iv);
pane.setStyle("-fx-background-color:black");
Scene sc = new Scene(pane);
splash.setScene(sc);
splash.show();
seqT.play();
return this;
}
}
And Main.java
package application;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("main.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add( getClass().getResource("application.css").toExternalForm());
primaryStage.setTitle("Flight");
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("airplane.png")));
primaryStage.setScene(scene);
Splash splash = new Splash();
splash.show();
primaryStage.toBack();
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Now I want to make a Splash screen appear before the main program is launched. How do I modify Main.java so that my code can show the splash screen for its entire duration before fading away and opening the main program?
I made a few changes to your code. I changed the Splash variable to Scene. I then added methods to get the Scene and the SequentialTransition. I set the SequentialTransition onFinished method in the main.
.
Main
import java.io.IOException;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
public class JavaFXApplication266 extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Splash splash = new Splash();
splash.show();
stage.setScene(splash.getSplashScene());
splash.getSequentialTransition().setOnFinished(e -> {
Timeline timeline = new Timeline();
KeyFrame key = new KeyFrame(Duration.millis(800),
new KeyValue(splash.getSplashScene().getRoot().opacityProperty(), 0));
timeline.getKeyFrames().add(key);
timeline.setOnFinished((event) -> {
try {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
//
Scene scene = new Scene(root);
stage.setScene(scene);
}
catch (IOException ex) {
ex.printStackTrace();
}
});
timeline.play();
});
//
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
Splash Class
import javafx.animation.*;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.Font;
import javafx.util.Duration;
/**
* This is my own splash screen, that I made myself.
*
*/
public class Splash
{
static Scene splash;
static Rectangle rect = new Rectangle();
final private Pane pane;
final private SequentialTransition seqT;
public Splash()
{
pane = new Pane();
pane.setStyle("-fx-background-color:black");
splash = new Scene(pane);
seqT = new SequentialTransition();
}
public void show()
{
/*
* Part 1:
* This is the rolling square animation.
* This animation looks cool for a loading screen,
* so I made this. Only the lines of code for fading
* from Stack Overflow.
*/
//rectangle insertion
int scale = 30;
int dur = 800;
rect = new Rectangle(100 - 2 * scale, 20, scale, scale);
rect.setFill(Color.AQUAMARINE);
//actual animations
//initialising the sequentialTranslation
//umm, ignore this, just some configs that work magic
int[] rotins = {scale, 2 * scale, 3 * scale, 4 * scale, 5 * scale, -6 * scale, -5 * scale, -4 * scale, -3 * scale, -2 * scale};
int x, y;
for (int i : rotins) {
//rotating the square
RotateTransition rt = new RotateTransition(Duration.millis(dur), rect);
rt.setByAngle(i / Math.abs(i) * 90);
rt.setCycleCount(1);
//moving the square horizontally
TranslateTransition pt = new TranslateTransition(Duration.millis(dur), rect);
x = (int) (rect.getX() + Math.abs(i));
y = (int) (rect.getX() + Math.abs(i) + (Math.abs(i) / i) * scale);
pt.setFromX(x);
pt.setToX(y);
//parallelly execute them and you get a rolling square
ParallelTransition pat = new ParallelTransition();
pat.getChildren().addAll(pt, rt);
pat.setCycleCount(1);
seqT.getChildren().add(pat);
}
//playing the animation
seqT.play();
//lambda code sourced from StackOverflow, fades away stage
seqT.setNode(rect);
//The text part
Label label = new Label("Flight");
label.setFont(new Font("Verdana", 40));
label.setStyle("-fx-text-fill:white");
label.setLayoutX(140);
label.setLayoutY(70);
Label lab = new Label("Launching...");
lab.setFont(new Font("Times New Roman", 10));
lab.setStyle("-fx-text-fill:white");
lab.setLayoutX(170);
lab.setLayoutY(180);
//A complimentary image
Image image = new Image("https://s3.amazonaws.com/media.eremedia.com/uploads/2012/08/24111405/stackoverflow-logo-700x467.png");
ImageView iv = new ImageView(image);
iv.setFitWidth(32);
iv.setFitHeight(32);
iv.setX(174);
iv.setY(130);
//now adding everything to position, opening the stage, start the animation
pane.getChildren().addAll(rect, label, lab, iv);
seqT.play();
}
public Scene getSplashScene()
{
return splash;
}
public SequentialTransition getSequentialTransition()
{
return seqT;
}
}
After Splash Controller
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
/**
*
* #author blj0011
*/
public class FXMLDocumentController implements Initializable
{
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event)
{
System.out.println("You clicked me!");
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
// TODO
}
}
After Splash FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication266.FXMLDocumentController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>

ScrollPane doesnt "know" when to expand

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();
});
}

Get dynamic elements from JavaFX container

i created a simple GUI, using JavaFX with fxml. One part of the program is using fxml and other generate dynamic GUI. So, i created javafx.scene.controller.Accordion with fxml and add panes inside it with code:
#FXML
Accordion accordionPlant;
public void init(){
accordionPlant.getPanes().add(createTitledPanePlant(1));
accordionPlant.getPanes().add(createTitledPanePlant(2));
}
protected TitledPane createTitledPanePlant(int index){
TilePane tile = new TilePane(Orientation.HORIZONTAL, 5, 5);
Label typeLabel = new Label("Тип выпуска");
TextField typeText = new TextField();
VBox typeContainer = new VBox(typeLabel,typeText);
Label bankLabel = new Label("Берег");
Tooltip.install(bankLabel, new Tooltip("Берег, с которого производится выпуск"));
TextField bankText = new TextField();
VBox bankContainer = new VBox(bankLabel,bankText);
tile.getChildren().addAll(typeContainer, bankContainer);
TitledPane titledPane = new TitledPane("Параметры выпуска " + index, tile);
return titledPane;
}
After that user click on the button and the program should find all TextField and calculate their value. So how it must be done correctly or comfortable?
I tried to get all panes of accordion:
void test(){
for (TitledPane pane: accordionPlant.getPanes()) {
pane.getChildrenUnmodifiable().get(0); //blah, blah
}
}
but i think it isn't a good idea, especially using index. I need only textfields. Is there any way of implements of ?
If you have dynamic elements, why not just store them in a List when you create them? That way you don't have to try to get them from a container. You are probably only interested in the Controller, but I posted the whole app. If you look in createTitledPanePlant you can see where I add the TextFields to the List -> List<TextField> textFieldContainer = new ArrayList();
Main
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication115 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);
}
}
Controller
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Orientation;
import javafx.scene.control.Accordion;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
/**
*
* #author blj0011
*/
public class FXMLDocumentController implements Initializable
{
#FXML
private Label label;
#FXML
private Accordion accordionMain;
List<TextField> textFieldContainer = new ArrayList();
#FXML
private void handleButtonAction(ActionEvent event)
{
//Sum TextField
double total = 0;
for (TextField node : textFieldContainer) {
double value;
try {
value = Double.parseDouble(node.getText());
}
catch (NumberFormatException e) {
value = 0;
}
total += value;
}
label.setText("Sum: " + total);
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
// TODO
accordionMain.getPanes().add(createTitledPanePlant(1));
accordionMain.getPanes().add(createTitledPanePlant(2));
}
protected TitledPane createTitledPanePlant(int index)
{
TilePane tile = new TilePane(Orientation.HORIZONTAL, 5, 5);
Label typeLabel = new Label("Тип выпуска");
TextField typeText = new TextField();
textFieldContainer.add(typeText);//Add your textField to the container when you create it
VBox typeContainer = new VBox(typeLabel, typeText);
Label bankLabel = new Label("Берег");
Tooltip.install(bankLabel, new Tooltip("Берег, с которого производится выпуск"));
TextField bankText = new TextField();
textFieldContainer.add(bankText);//Add your textField to the container when you create it
VBox bankContainer = new VBox(bankLabel, bankText);
tile.getChildren().addAll(typeContainer, bankContainer);
TitledPane titledPane = new TitledPane("Параметры выпуска " + index, tile);
return titledPane;
}
}
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Accordion?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="486.0" prefWidth="431.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141" fx:controller="javafxapplication115.FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="179.0" layoutY="437.0" onAction="#handleButtonAction" text="Sum nodes" />
<Label fx:id="label" layoutX="163.0" layoutY="37.0" minHeight="16" minWidth="69" prefHeight="17.0" prefWidth="106.0" />
<Accordion fx:id="accordionMain" layoutX="115.0" layoutY="72.0" prefHeight="310.0" prefWidth="202.0" />
</children>
</AnchorPane>

How can I add 3D surface to the AnchorPane

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.

Categories