Generate a custom object and apply custom fxml file in JavaFX

-1

I want to program a "Simple UML Editor"

Use Case

Click UML canvas, and generate a UML shape. Cursor is at the UML shape's top left after generated. As this image.


Here is sample code.

Main.java

package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("mainView.fxml"));
            Scene scene = new Scene(root);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

mainView.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.layout.*?>


<Pane fx:id="canvas" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onMouseClicked="#onMouseClicked" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainViewController" />

MainViewController.java

package application;

import javafx.fxml.FXML;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;

public class MainViewController {

    @FXML Pane canvas;

    @FXML private void onMouseClicked(MouseEvent event) {
        myCircle c = new myCircle();

        c.setLayoutX(event.getX());
        c.setLayoutY(event.getY());
        canvas.getChildren().add(c);
    }
}

myCircle.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.shape.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.Scene?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="40.0" prefWidth="40.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Circle fx:id="circle" fill="DODGERBLUE" layoutX="20.0" layoutY="20.0" radius="20.0" stroke="BLACK" strokeType="INSIDE" />
   </children>
</Pane>

myCircle.java

package application;

import java.io.IOException;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Parent;
import javafx.scene.shape.Circle;

public class myCircle extends Parent {

    @FXML Circle circle;

    public myCircle() {
        // TODO Auto-generated constructor stub
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("myCircle.fxml"));
        //fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);
        try {
            fxmlLoader.load();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        this.getChildren().add(circle);

        System.out.println("generate myCircle");
    }
}

Questions

  1. In file: myCircle.java. I can't add code fxmlLoader.setRoot(this);, or it will show the error message: "Root value already specified." Is the root node dynamic loading? (I didn't use <fx:root> or setRoot()) If it is dynamic loading, which one is my current root node?

  2. In file: myCircle.java. I must add line this.getChildren().add(circle);, or there is no circle generate. Why? I think there is some important detail I don't know...

  3. I need centerXProperty() to implement binding line relative feature, but there is some problem. My custom UML shape apply and load custom fxml file, I couldn't get real centerXProperty. I print centerXProperty messege: DoubleProperty [bean: Circle[id=circle, centerX=0.0, centerY=0.0, radius=20.0, fill=0x1e90ffff, stroke=0x000000ff, strokeWidth=1.0], name: centerX, value: 0.0]. The value always is 0.0 no matter what. How could I do?

I don't want to type spaghetti code.

javafx
javafx-8
fxml
asked on Stack Overflow Jan 19, 2017 by scps940707 • edited Dec 6, 2018 by Cœur

1 Answer

0
  1. The root element of your FXML file is a <Pane> element, which is basically an instruction to the FXMLLoader to create a Pane instance, which becomes the root. Thus when you try to call fxmlLoader.setRoot(...) it complains, because there is already a root specified in the FXML file. To fix this, you need to use <fx:root...> as the root element:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.shape.*?>
    <?import java.lang.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.scene.Scene?>
    
    <fx:root type="Pane" maxHeight="-Infinity" maxWidth="-Infinity" 
        minHeight="-Infinity" minWidth="-Infinity" 
        prefHeight="40.0" prefWidth="40.0" 
        xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
       <children>
          <Circle fx:id="circle" fill="DODGERBLUE" layoutX="20.0" layoutY="20.0" radius="20.0" stroke="BLACK" strokeType="INSIDE" />
       </children>
    </fx:root>
    

    In order for fxmlLoader.setRoot(this) to work, this must be an instance of the class specified by the type attribute to <fx:root>, i.e. you must make myCircle a subclass of Pane:

    package application;
    
    import java.io.IOException;
    
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.layout.Pane;
    import javafx.scene.shape.Circle;
    
    public class myCircle extends Pane {
    
        @FXML Circle circle;
    
        public myCircle() {
            // TODO Auto-generated constructor stub
            FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("myCircle.fxml"));
            fxmlLoader.setRoot(this);
            fxmlLoader.setController(this);
            try {
                fxmlLoader.load();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            // circle is now added to this by the FXML. 
            // So next line is no longer needed:
            // this.getChildren().add(circle);
    
            System.out.println("generate myCircle");
        }
    }
    
  2. Is fixed by fixing fxmlLoader.setRoot(this). The circle is a child of the Pane that is the root of the FMXL file. Previously, it wasn't added to the myCircle instance unless you did so explicitly.

  3. You never change the centerX and centerY properties, so they will always be 0. Did you mean to use centerX="20" instead of layoutX="20" in the FXML? If you want to expose them for binding/setting/etc you can do so in your myCircle class:

    package application;
    
    import java.io.IOException;
    
    import javafx.beans.property.DoubleProperty ;
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.layout.Pane;
    import javafx.scene.shape.Circle;
    
    public class myCircle extends Pane {
    
        @FXML Circle circle;
    
        public myCircle() {
            // TODO Auto-generated constructor stub
            FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("myCircle.fxml"));
            fxmlLoader.setRoot(this);
            fxmlLoader.setController(this);
            try {
                fxmlLoader.load();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            // circle is now added to this by the FXML. 
            // So next line is no longer needed:
            // this.getChildren().add(circle);
    
            System.out.println("generate myCircle");
        }
    
        public DoubleProperty centerXProperty() {
            return circle.centerXProperty();
        }
    
        public final double getCenterX() {
            return centerXProperty().get();
        }
    
        public final void setCenterX(double centerX) {
            centerXProperty().set(centerX);
        }
    
        // same for centerY...
    }
    

    You could also consider just making MyCircle a subclass of Circle, so that you simply inherit the centerX and centerY properties. i.e.

    package application;
    
    import java.io.IOException;
    
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.shape.Circle;
    
    public class myCircle extends Circle {
    
        public myCircle() {
            // TODO Auto-generated constructor stub
            FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("myCircle.fxml"));
            fxmlLoader.setRoot(this);
            fxmlLoader.setController(this);
            try {
                fxmlLoader.load();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            System.out.println("generate myCircle");
        }
    }
    

    and then

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.shape.*?>
    <?import java.lang.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.scene.Scene?>
    
    <fx:root type="Circle" fill="DODGERBLUE" layoutX="20.0" layoutY="20.0" 
        radius="20.0" stroke="BLACK" strokeType="INSIDE"
        xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
    </fx:root>
    

    though I'm not sure if this still gives you all the functionality you want.

answered on Stack Overflow Jan 19, 2017 by James_D

User contributions licensed under CC BY-SA 3.0