简体   繁体   中英

Accessing a Canvas in JavaFX scene using FXML Loader

So I am trying to make a scene that is a pane containing a canvas. This canvas will need to be inside this pane as I am going to display information along side it. I have used scenebuilder to create a pane then placed a canavas called "drawing" inside of it. I then load the scene and attempt to then draw in the canvas however I get and error saying the canvas is null. I know there are answers to similar questions however they do not answer my question in my context I don't feel. If they do could you please explain how as I don't understand.

package uk.ac.rhul.cs3821;

import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.stage.Stage;

public class TurtleView extends Application {

  private static volatile TurtleView instance = null;
  
  Turtle turtle = new Turtle(0,0,0);
  
  @FXML
  private Canvas drawing;
  
  private GraphicsContext gc;
  
  @FXML
  void initialize() {
    instance = this;
  }

  /**
   * Creates a JavaFX instance and Launches the JavaFX application.
   * 
   * @return the JavaFX instance running
   */
  public static synchronized TurtleView getInstance() {
    if (instance == null) {
      new Thread(() -> Application.launch(TurtleView.class)).start();
      // Wait until the instance is ready since initialise has executed.
      while (instance == null) {// empty body
      }
    }

    return instance;
  }
  
  @Override
  public void start(Stage primaryStage) throws Exception {
    Parent root = FXMLLoader.load(RuleView.class.getResource("TurtleView.fxml"));
    Scene scene = new Scene(root, 800, 500);
    primaryStage.setScene(scene);
    primaryStage.show();
    gc = drawing.getGraphicsContext2D();
  }
    

}

Issues you should address

  1. The JavaFX application class is responsible for implementing the application lifecycle .

    • That is plenty of responsibilities for one class.

    • You should not ask it to do more than that.

    • In small demo applications often the application class will do everything, but that is just because they are small demos, in anything other than that, separate functionality across different classes.

  2. Never have an application class also act as a controller.

    • When you look at the application lifecycle definition from earlier, you can see that there should only be one instance of an application in a VM, which is created when launch() is invoked, which must only be invoked once.

    • By default an FXML loader will create an instance on the fx:controller class defined in the FXML. If that class is an application then the controller will create another application instance, but it will not know about the launched instance which will be very confusing to reason about as some fields will be initialized in one instance and some fields initialized in another instance.

  3. The getInstance() method is quite strange.

    • I am not sure what it is for and why you have it.

    • Whatever the reason for it, there is probably a better way of handling it.

    • I won't talk more about it here, but you might want to ask a new question with a specific mcve just for around the instance management that explains what you are doing and why and asks about a better approach to achieve your well-explained goal.

  4. Because an application is not a controller it should never contain:

    • a controller initialize() method (the main(), init() and start() methods are hooks for application initialization).

    • No @FXML annotated fields as those fields should only exist in a controller.

So what shoud you do?

See the example hello application in this answer:

Just ignore all the exe packing stuff in the answer, that is irrelevant, and look at everything else there in terms of code structure, file layout, build tool usage, etc. and follow that (that would be the "file tree" and "files" sections of the answer). It demonstrates how to structure a simple hello world application using FXML.

The linked example hello world JavaFX application was initially created by the Idea New JavaFX Project wizard . If you have idea, you can just run that to create a new project then modify it for your implementation, otherwise you can copy the code from the linked answer into a new project in your IDE and modify that.

Subjective design comments

A couple of additional design points, which are not that important and you can ignore if you wish. Design is subjective and there are different implementation strategies which can all be OK. You know your app better, so choose the best one for you.

  • You can have multiple FXML files for your app and not all nodes need to be defined in FXML, you can mix the two different node creation systems.

  • Perhaps have a class (non-FXML) which creates a canvas and responds to events and drawing commands on the canvas, and a separate class (an FXML controller) for managing the UI controls that interfaces with the drawing class.

  • The only @FXML annotated field in the class is the canvas.

    • If a class only contains a single node instance, it will be easier to just create the node in code not using FXML.

    • FXML is only really useful when you have a more complicated layout with lots of nodes.

    • This is subjective, you could still have a controller with just one @FXML field and it would be fine to implement that way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM