2

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!

Treeno1
  • 139
  • 1
  • 2
  • 18

2 Answers2

2

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/

1

This is a non-trivial task. I am not going to provide code for it here, but only a general approach outline:

  1. Serialize your shapes to FXML (you will need to code the serializer yourself).
  2. 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).
  3. 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.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • I would really prefer to do this the FX way rather than Fxml. Is it possible with the same approach? – Treeno1 Apr 15 '16 at 21:07
  • I don't understand what you mean. What is "the FX way"? – jewelsea Apr 15 '16 at 21:08
  • I guess the best way for me to explain it - http://www.javafxtutorials.com/tutorials/switching-to-different-screens-in-javafx-and-fxml/. essentially a demo about switching frames using two approaches, with FXML using scene builder or just coding javaFX. As soon as i heard FXML I assumed you meant using scene builder, as that has really been my only experience wit FXML. one I'm not very good with unfortunately. So similar to that example. Is there a way I can just code it without using FXML is what i meant to say? – Treeno1 Apr 15 '16 at 21:17
  • I really appreciate the thorough and thought out response, having consulted a few other sources on the topic of object serialisation, it appears there is no real simple way of doing it, as you so rightly mentioned "Not trivial". Unfortunately it doesn't seem JavaFX support serialisation, but there is a way of deserialising using FXMLLoader. As this was only a small part of a bigger program I feel I may have to find another way round this problem as it is connected to a project deadline, which is frustrating. – Treeno1 Apr 15 '16 at 23:49