JavaFX open a new scene

2020-06-06 04:47发布

问题:

I want my code to work such that when I click on a Button, a new scene opens, but it doesn't work and I don't know why.

public void start(Stage primaryStage) throws Exception {
    window = primaryStage;

    Parent root = FXMLLoader.load(getClass().getResource("FXML/LoginScene.fxml"));
    scene = new Scene(root,400,400);
    openScene = new OpenScene(writer);
    window.setScene(scene);
    window.show();


}
public static void main(String[] args){
    launch(args);
}
@FXML protected void btnConnect(ActionEvent event) {
    System.out.println("hallo");
    try {
        openScene.start(window);
    } catch (Exception e) {

        e.printStackTrace();
    }
}

The GUI successfully shows up, but when I press the Button, it throws an Exception.

public class OpenScene extends Application{
    PrintWriter writer;

    @Override
    public void start(Stage window) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXML/OpenScene.fxml"));
        Scene scene =  new Scene(root, 200 ,200);
        window.setScene(scene);
        window.show();

    }
    public OpenScene(PrintWriter writer){
        this.writer = writer;
    }
}

Update

I tried to separate the application class from the controller class as in James_D's answer below, but I got the following exception:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1762)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1645)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8216)
at javafx.scene.control.Button.fire(Button.java:185)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3724)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3452)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1728)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2461)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:348)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:273)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:382)
at com.sun.glass.ui.View.handleMouseEvent(View.java:553)
at com.sun.glass.ui.View.notifyMouse(View.java:925)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1759)
... 43 more
Caused by: java.lang.NullPointerException
at MainController.btnConnect(MainController.java:22)
... 53 more

回答1:

It looks like you are trying to use your main application class as the controller class. This is going to be confusing, and you should avoid it. Here is what happens:

When you start up the application, it calls launch(...). The launch method, inherited from Application, will do a bunch of important "housekeeping", such as starting the JavaFX toolkit and the JavaFX Application Thread. It then creates an instance of your application subclass, creates an initial Stage and calls the start(...) in that instance.

In your start(...) method, you initialized a couple of instance variables (window and openScene), and loaded an FXML file, displaying its contents. The FXMLLoader.load(...) method does the following:

  • Creates UI elements based on the XML elements in the file
  • Creates an instance of the class specified by the fx:controller attribute in the FXML file
  • Injects any @FXML-annotated fields into the controller instance
  • Registers any event handlers specified in the FXML file

Notice that if you specify the same class for the application and for the controller, two instances of that class will be created. One is created by the launch method, and one is created by the FXMLLoader. Note that only the instance created by the launch method has had the start(...) method invoked. Since you initialize the instance variables in the start(...) method, those variables are not initialized in the instance created by the FXMLLoader. So in the instance created by the FXMLLoader (the "controller instance", if you like), window and openScene are not initialized. Hence the line

    openScene.start(window);

will throw a NullPointerException.

Since the application and controller really have completely different roles, you should separate them into different classes. This will make things far less confusing. Note that you can always find the window in which a node is displayed by calling

anyNode.getScene().getWindow();

so there is no need to cache the Stage instance.

So:

public class MainApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        // change LoginScene.fxml so it now has fx:controller="LoginController"
        Parent root = FXMLLoader.load(getClass().getResource("FXML/LoginScene.fxml"));

        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

and use a different class for the controller:

public class LoginController {

    private OpenScene openScene ;

    @FXML
    private Button connectButton ; // needs fx:id in fxml file...

    public void initialize() throws Exception {
        PrintWriter writer = ... ;
        openScene = new OpenScene(writer);
    }

    @FXML // handler for connect button:
    private void btnConnect() throws Exception {
        Stage stage = (Stage) connectButton.getScene().getWindow();
        openScene.start(stage);
    }
}

Note also that there is no need for your OpenScene class to be an Application subclass: you only need one such class per application:

public class OpenScene {

    private final PrintWriter writer ;

    public OpenScene(PrintWriter writer) {
        this.writer = writer ;
    }

    // doesn't need to be called "start" any more...    
    public void start(Stage window) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXML/OpenScene.fxml"));
        Scene scene =  new Scene(root, 200 ,200);
        window.setScene(scene);
        window.show();
    }
}   


标签: javafx scene