Saving shapes to file drawn on border pane in javafx - java

I have a basic application, sort of like a toy box, it doesn't really do anything except let you move shapes around a screen, if you double click the shape, it brings it into the foreground. There is also a menu with menu Items "New, "open", "Save" -
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class RandCMove extends Application
{
public static void main(String [] args)
{
launch(args);
}
public void start(Stage primaryStage)
{
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar();
menuBar.prefWidthProperty().bind(primaryStage.widthProperty());
Menu fileMenu = new Menu("File");
MenuItem newMenuItem = new MenuItem("New");
MenuItem openMenuItem = new MenuItem("Open");
MenuItem saveMenuItem = new MenuItem("Save");
MenuItem exitMenuItem = new MenuItem("Exit");
fileMenu.getItems().addAll(newMenuItem,openMenuItem,saveMenuItem,
new SeparatorMenuItem(),exitMenuItem);
menuBar.getMenus().add(fileMenu);
Rectangle rect = new Rectangle();
rect.setWidth(200);
rect.setHeight(200);
rect.setArcHeight(20);
rect.setArcWidth(20);
rect.setFill(Color.RED);
rect.setX(200);
rect.setY(100);
root.setTop(menuBar);
root.getChildren().add(rect);
Circle circle = new Circle(
300,300,100);
Text text = new Text(150,150,"Text");
Font phosphate = Font.font("Phosphate",150);
text.setFont(phosphate);
text.setTranslateY(circle.getBoundsInParent().getMinY()+10);
root.getChildren().add(text);
//Positions the circle under the rectangle
circle.setTranslateY(rect.getBoundsInParent().getMinY()+30);
root.getChildren().add(circle);
// Moves shapes depending on if the cursor on the particular shape
// Brings shape to the front using double click
root.setOnMouseMoved(e ->
{
if(rect.contains(e.getX(),e.getY()))
rect.setOnMouseDragged(f ->{
rect.setX(f.getX());
rect.setY(f.getY());
});
rect.setOnMouseClicked(f ->{
if(f.getClickCount() >= 2)
rect.toFront();
});
if(circle.contains(e.getX(),e.getY()))
circle.setOnMouseDragged(f->{
circle.setCenterX(f.getX());
circle.setCenterY(f.getY());
});
circle.setOnMouseClicked(f ->{
if(f.getClickCount() >= 2)
circle.toFront();
});
if(text.contains(e.getX(),e.getY()))
text.setOnMouseDragged(f ->{
text.setX(f.getX());
text.setY(f.getY());
});
text.setOnMouseClicked(f ->{
if(f.getClickCount() >= 2)
text.toFront();
});
});
Scene scene = new Scene(root,600,600);
primaryStage.setScene(scene);
primaryStage.show();
}
}
I want to be able to save the current state of the shapes on screen, and have them load up as they were upon pressing "Open".
I can't seem to find anything useful on this, can anyone provide some guidance or point me in the right direction? Any assistance would be appreciated!

Applications require preference and configuration data to adapt to the needs of different users and environments. The java.util.prefs package provides a way for applications to store and retrieve user and system preference and configuration data. The data is stored persistently in an implementation-dependent backing store. There are two separate trees of preference nodes, one for user preferences and one for system preferences.
More at:
http://docs.oracle.com/javase/8/docs/technotes/guides/preferences/

This is a non-trivial task. I am not going to provide code for it here, but only a general approach outline:
Serialize your shapes to FXML (you will need to code the serializer yourself).
Each time the user edits the shapes, serialize the to FXML again and save the FXML as a preference (as outlined in Frank Fotangs answer).
When the user first opens the application, read the current preference containing the FXML for the current shapes and load them up using an FXMLLoader.
The same technique can be used for implementing your Open and Save menu options, only instead of writing the serialized data to a preference you will write it to a file location chosen by a FileChooser.
I would really prefer to do this the FX way rather than Fxml.
I think you misunderstand the problem that you need to solve. There is no "FX way" of doing this.
Your problem is that you need to serialize and deserialize nodes from a scene graph, for which the core JavaFX API does not provide a full implementation.
When you create a scene graph using JavaFX using JavaFX APIs, it is an in-memory representation of your scene, only available while your program is executing. What you need to be able to do is persist that scene to a file so that when you shut down your application the data representing the scene is stored somewhere. Then you need to read the data from the file and recreate the scene in memory when the user starts the application back up again. The only way to do this is to serialize the objects to storage when needed and deserialize the objects from storage when the application starts back up again. Read the wikipedia article on serialization to understand the conceptual process.
Now you don't need to use FXML for your serialized format, you could use other formats such as JSON or YAML and other serialization support libraries such as Jackson. However, to me, FXML seems the most logical format to choose because JavaFX ships with the FXMLLoader, which provides an easy to use API to transfer your serialized data (an FXML file representing your scene) into an in-memory structure (the scene graph), via a simple call to the load method.
What makes this task tricky rather than trivial is that the JavaFX API only ships with an FXMLLoader, it does not ship with a corresponding class (e.g. an FXMLSaver), which does the opposite work of taking the in-memory structure (the scene graph) and creating serialized data from it (an FXML file). So you will need to write such logic yourself.
Now, SceneBuilder is an open source project and does include logic to save a scene graph to an FXML file. For instance you can find source code for an FXMLSaver in the SceneBuilder source. Perhaps you could use or adapt that code to save your scene graph objects into a serialized format. In doing so, you wouldn't be using the scene builder application to create and save your scene, instead you would be adapting SceneBuilder source code to be included in your own application which would be creating and saving your scene.

Related

JavaFX ImageView display GIFanimation high GPU [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I am creating JavaFX application and made a loading scene. In this scene i want to show some text like loading or initializing and add some loading spinner animated image as a visual notification that something is loading.
I generate a loading .gif animation created via https://loading.io/ and also downloaded it like frames of animation (multiple .png files).
I noticed that in the JavaFX Scene Builder 8.5.0 when I set the .gif in ImageView, my GPU in Windows task manager went up to 20%, on NVidia GeForce RTX 2070 graphic card and in my opinion this is a problem. I tested with a javafx desktop application, to rule out that is not a problem generated by the scene builder application and got similar results.
The next step that i tried is to create my custom animation using javafx.​animation.Timeline. This is the initializing function that i call from the controller initialize(URL location, ResourceBundle resources) function. This function doesn't use the .gif image, it uses the multiple .png images of the same .gif image as frames.
private void initializeAnimation() {
imgLoading.setCache(true);
imgLoading.setCacheHint(CacheHint.SPEED);
Image[] images = new Image[31];
for(int i = 0; i <= 30; i++){
images[i] = new Image(getClass().getResourceAsStream("/es/main/gui/javafx/images/loading/frame-" + i + ".png"));
}
Timeline timeLine = new Timeline();
Collection<KeyFrame> frames = timeLine.getKeyFrames();
Duration frameGap = Duration.millis(100);
Duration frameTime = Duration.ZERO;
for (Image img : images) {
frameTime = frameTime.add(frameGap);
frames.add(new KeyFrame(frameTime, e -> imgLoading.setImage(img)));
}
timeLine.setCycleCount(Timeline.INDEFINITE);
timeLine.play();
}
In the first version i didn't call .setCache() and .setCacheHint() functions, I added them later while testing different variations of the same code. Also i tried adding
-Dprism.forceGPU=true -Dsun.java2d.opengl=true -Dprism.order=es2,es1,sw,j2d
as VMOption or some variants that I have read on this forum on improving graphic related settings for java. At the end, after all the changes, results in my task manager didn't change drastically. In the current version i use up to 17% of my GPU on this scene.
When the scene ends, in the next scene with no .gif images or Timeline's my GPU drops to almost 0%.
Running configurations:
Processor: i9-9900KF
Graphic card: GeForce RTX 2070
Java version "1.8.0_251"
JavaFX version "8.0.251-b08"
Short summary question: How to display animated .gif images correctly in JavaFX without having drastic overhead on the GPU (or CPU when with integrated graphics).
(Edit) 14.09.2020 - Java naming conventions
First thing I didn't noticed that i was not using the same size on my ImageView, so the first thing i changed for testing is adjusting the ImageView the same size as my .gif image (same width and height on both ImageView and .gif image). With this change the GPU percentage lowered to about %5.
As suggested I also upgrading the Java and JavaFX versions:
Tried using the jdk 1.8.0_261 with JavaFX built in version 8.0.261-b12 and got similar results.
Tried using jdk 14.0.2 with JavaFX version 15+9 (latest openjfx-15) and still got similar results.
Short summary: Upgrading the Java and JavaFX version didn't change anything relating this issue. Using the same size helped, but I think I can improve even better with your help.
My guess is you'd probably see some performance gains if you used one or more RotateTransition rather than key frame animation.
Here's a simple example using multiple transitions in a ParallelTransition:
import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 500, 200);
stage.setScene(scene);
Rectangle rect = new Rectangle (100, 40, 100, 100);
rect.setArcHeight(50);
rect.setArcWidth(50);
rect.setFill(Color.VIOLET);
RotateTransition rt = new RotateTransition(Duration.millis(3000));
rt.setByAngle(180);
rt.setAutoReverse(true);
FadeTransition ft = new FadeTransition(Duration.millis(3000));
ft.setFromValue(1.0);
ft.setToValue(0.3);
ft.setCycleCount(4);
ft.setAutoReverse(true);
ParallelTransition pt = new ParallelTransition(rect, ft, rt);
pt.play();
root.getChildren().add(rect);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

How to convert an image into a shape in Java?

I'm currently working on a stock market program that teaches students how to interact with the stock market. An issue that I'm working on right now revolves around the issue of efficiency and memory. I make all of my 2D icons (such as a settings icon, portfolio icons, etc) in adobe illustrator, I export those files as png files, and throw them into my program. I currently use JavaFX, and one of the features of JavaFX is what's called imageView, which is a method that handles the opening and viewing of images.
So let's say that the user would like to press on the Settings icon in the game, I would like to change the settings icon to a darker or lighter color when the user hovers over that icon in order to provide the user with a better user experience (UX) at the moment, I use two different images and remove one from the frame, and replace it with another, which is highly inefficient.
I know that JavaFX has a Shape class that inherits many methods such as Fill, or setFill, but these methods can only affect those of the Shape class.
My question is, "How can I convert an image that is imported into the project, into something that I can use methods such as setFill and Fill on"
If you're only aiming for basic changes such as darkening or lightning your icons, you can look at the Effects part of javaFx, you can read about it here, or else you can import your images as SVG as suggested in the comments
If you're planning to do it using Effects, you can achieve a darken-on-hover effect using the ColorAdjust Effect by setting the brightness value to something negative (the brightness in ColorAdjust ranges between -1 and +1 where 0 is the default) as in the following example
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage s) {
ImageView image = new ImageView(new Image("img.png"));
ColorAdjust darker = new ColorAdjust();
darker.setBrightness(-.3);
image.setOnMouseEntered(e-> {
image.setEffect(darker);
});
image.setOnMouseExited(e-> {
image.setEffect(null);
});
s.setScene(new Scene(new StackPane(image)));
s.show();
}
public static void main(String[] args) {
launch(args);
}
}
Changing the color of an image might involve adjusting the hue using ColorAdjust by setting the hue value
ColorAdjust hueShift = new ColorAdjust();
hueShift.setHue(-.3);
image.setOnMouseEntered(e-> {
image.setEffect(hueShift);
});
image.setOnMouseExited(e-> {
image.setEffect(null);
});
You can combine effects by setting effects as input to other effects, so for example, if you want to darken a Node and blur it at the same time you can set the blur effect as input to the darkening colorAdjust
GaussianBlur blur = new GaussianBlur();
blur.setRadius(10);
ColorAdjust darker = new ColorAdjust();
darker.setBrightness(-.3);
darker.setInput(blur);
image.setOnMouseEntered(e-> {
image.setEffect(darker);
});
image.setOnMouseExited(e-> {
image.setEffect(null);
});

Why does instantiating a JavaFX scene control corrupt the layout

I'm trying to create a simple data entrty application with javafx and ran into a problem when adding a scene control. The display loses it's fill colour even BEFORE i've added the control to the scene! Simply instantiating the control breaks it.
I was running on Oracle java 8 on windows, but I;ve tried OpenJDK 8 on Windows and OpenJDk/OpenJFX 13 on linux and all behave identically. I stripped out the code to the bear minmium to recreate the problem.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class BasicTest extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle r = new Rectangle();
r.setWidth(200);
r.setHeight(50);
r.setFill(Color.BLUE);
r.setStroke(Color.WHITE);
r.setStrokeWidth(2);
Text t = new Text();
t.setText("Confirm");
t.setFill(Color.WHITE);
t.setFont(Font.font("null", 40));
StackPane sp = new StackPane(r);
sp.getChildren().add(t);
sp.setMaxWidth(200);
t.setTranslateY(-2);
Label b = new Label("Click me");//Comment this line out after first run
Scene scene = new Scene(sp, 300, 200);
scene.setFill(Color.BLUE);
primaryStage.setScene(scene);
primaryStage.show();
}
}
With the label commented out the scene background is blue so I get a white "Confirm" with white outline. Just adding the label constructor will make the scene background go grey.
Thanks for all the comments. Many apologies if my wording was confusing. I did add some images to show the difference but they seem to have disappeared. Anyway my thanks to #Matt for their very succinct description (which I wish I'd have thought of!). I like the mouse click idea too.
I was probably being very naughty asking this "question" on SO when I firmly believe this is a bug in javafx. Instantiating an object that goes no where near (yet) the scene graph should have no effect on it in my opinion - CSS or no CSS. I will raise a bug against javafx.
However I knew the power of SO would help me and it has!
Thanks to the clue by #jewelsea I simply replaced my scene.setFill() (which was only a test anyway) with CSS and the problem is circumvented. I can even add the control to the scene now and it works as expected. From the JavaFX CSS Reference:
"The Scene object has no settable CSS properties, nor does it have any pseudo-classes. However, the root node of the scene is assigned the style class "root" (in addition to style classes already assigned to the node). "
So I set up in my css:
.root {
-fx-background-color: blue;
}
Another way to circumvent this is to simply set the background of the StackPane to BLUE:
sp.setBackground(new Background(new BackgroundFill(Color.BLUE, null, null)));

How to execute code during a drag and drop operation?

I have a node I want to implement drag and drop for (this object is the source not the target). I also want the object to move along with the mouse cursor. I managed to do both of these but not at the same time.
It appears that setOnDragDetected and setOnMouseDragged don't work well together. Consider a node with the following handlers:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect = new Rectangle(20, 20);
rect.setOnMousePressed(e -> System.out.println("Pressed"));
rect.setOnMouseDragged(e -> System.out.println("Dragged"));
rect.setOnDragDetected(e -> {
System.out.println("Detected");
ClipboardContent content = new ClipboardContent();
content.putString("something");
Dragboard db = rect.startDragAndDrop(TransferMode.ANY);
db.setContent(content);
});
Group subGroup = new Group(rect);
Scene scene = new Scene(subGroup, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Example.launch(args);
}
}
Now press the mouse on the node and move the mouse. This is the output:
Pressed
Dragged
Dragged
Dragged
Dragged
Dragged
Dragged
Detected
Once the drag is detected the MouseDragged handler stops.
How do I achieve what I described? One thing i noticed was maybe that I can use a onDragOver for the parent but i want the behavior to be in the node because that's where it really should be.
You are mixing two things up here. In short, when you call the startDragAndDrop method the system switches to drag and drop mode and Java stops delivering MouseEvent to rect.
The MouseEvent documentation has a section "Dragging gestures" which explains the three types of dragging gestures. Here is just a short summary:
simple press-drag-release - when a drag is detected Java continues to deliver MouseEvents to the node where the drag was detected.
full press-drag-release - you can call startFullDrag inside the handler you set with setOnDragDetected. Then Java also starts to deliver MouseDragEvents to other nodes (potential gesture targets).
platform-supported drag-and-drop - if you call startDragAndDrop inside the OnDragDetected handler, Java will stop to deliver MouseEvents and start to deliver DragEvents instead. This is used for drag and drop interaction with other applications.
It is not clear to me what you want to achieve, but as long as you do not want to drag something outside of your application, try using startFullDrag instead.
Also, it might be helpful to have a further look at the DragEvent and MouseDragEvent documentation.

Import STL file in JavaFX

My problem is that I am trying to import a 3D model from an STL file to a JavaFX application. I followed the code in this link How to create 3d shape from STL in JavaFX 8? and it's only working with the jewel file mentioned there, but I've tried with other STL files and it's not working!
I can't see why it's not working with the other files. Can anyone explain this?
Any help please, as soon as possible!
As you are already using an STL importer from this site, you will find in the same web a 3D model browser you can use to preview your models before importing them to your JavaFX application.
If they can't be imported with this browser, the problem may be related to a non valid STL format in your files.
If they are imported, then the problem may be in your application. Embed the call in a try-catch and post the exception you may enconter.
StlMeshImporter stlImporter = new StlMeshImporter();
try {
stlImporter.read(this.getClass().getResource("<STLfile>.stl"));
}
catch (ImportException e) {
e.printStackTrace();
return;
}
EDIT
If no exception is thrown while reading the model, the next step would be inserting the returned mesh into a MeshView and show it on our scene:
TriangleMesh mesh = stlImporter.getImport();
stlImporter.close();
MeshView mesh=new MeshView(cylinderHeadMesh);
Group root = new Group(mesh);
Scene scene = new Scene(root, 1024, 800, true);
Camera camera = new PerspectiveCamera();
scene.setCamera(camera);
primaryStage.setScene(scene);
primaryStage.show();
Since the model could be too small or too big for our scene (related to the camera and the point of view we are using), we should print the bounding box of our model, and then scale it up or down accordingly:
System.out.println("mesh: "+mesh.getBoundsInLocal().toString());
mesh.setScaleX(1d);
mesh.setScaleY(1d);
mesh.setScaleZ(1d);
Or we could change the camera parameters:
double max = Math.max(mesh.getBoundsInLocal().getWidth(),
Math.max(mesh.getBoundsInLocal().getHeight(),
mesh.getBoundsInLocal().getDepth()));
camera.setTranslateZ(-3*max);

Categories

Resources