JavaFX FXML communication between Application and

2019-02-11 12:43发布

I am trying to get and destroy an external process I've created via ProcessBuilder in my FXML application close, but it's not working. This is based on the helpful advice Sergey Grinev gave me here.

I have tried running with/without the "// myController.setApp(this);" and with "// super.stop();" at top of subclass and at bottom (see commented out/in for that line in MyApp), but no combination works.

This probably isn't related to FXML or JavaFX, though I imagine this is a common pattern for developing apps on JavaFX. I suppose I'm asking for a Java best practice for closing dependent processes in a UI-based app like this one (in this case: FXML / JavaFX based), where there is a controller class and an application class.

Can you explain what I'm doing wrong? Or better: advise what I should be doing instead? Thanks.

In my Application I do this:

public class MyApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader fxmlLoader = new FXMLLoader();
        Scene scene = (Scene)FXMLLoader.load(getClass().getResource("MyApp.fxml"));
        MyAppController myController = (MyAppController)fxmlLoader.getController();

        primaryStage.setScene(scene);
        primaryStage.show();

//        myController.setApp(this);
    }

    @Override
    public void stop() throws Exception {
//      super.stop();

      // this is called on fx app close, you may call it in an action handler too
      if (MyAppController.getScriptProcess() != null) {
        MyAppController.getScriptProcess().destroy();
      }
      super.stop();
    }
    public static void main(String[] args) {
        launch(args);
    }
}

In my Controller I do this:

public class MyAppController implements Initializable {

  private Application app;
  private static Process scriptProcess;


  public void setApp(Application a) {
    app = a;
  }

  public static Process getScriptProcess() {
    return scriptProcess;
  }
}

The result when I run with the "commented-out setApp()" not commented out (that is, left in the start method), is the following, immediately upon launch (the main Scene flashes, then disappears, then this dialog appears:

"JavaFX Launcher Error:
Exception while running Application"

And it gives an, "Exception in Application start method" in the console as well.

The result when I leave out the "commented-out code" in my MyApp above (that is, remove the "setApp()" from the start method), is that my app does indeed close, but gives this error when it closes:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1440)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:69)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:217)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:170)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:38)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:53)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:28)
    at javafx.event.Event.fireEvent(Event.java:171)
    at javafx.scene.Node.fireEvent(Node.java:6863)
    at javafx.scene.control.Button.fire(Button.java:179)
    at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:193)
    at com.sun.javafx.scene.control.skin.SkinBase$4.handle(SkinBase.java:336)
    at com.sun.javafx.scene.control.skin.SkinBase$4.handle(SkinBase.java:329)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:64)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:217)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:170)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:38)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:53)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:33)
    at javafx.event.Event.fireEvent(Event.java:171)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3324)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3164)
    at javafx.scene.Scene$MouseHandler.access$1900(Scene.java:3119)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1559)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2261)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:228)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:528)
    at com.sun.glass.ui.View.notifyMouse(View.java:922)
    at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
    at com.sun.glass.ui.gtk.GtkApplication$3$1.run(GtkApplication.java:82)
    at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1435)
    ... 44 more
Caused by: java.lang.NullPointerException
    at mypackage.MyController.handleCancel(MyController.java:300)
    ... 49 more
Clean up...

1条回答
虎瘦雄心在
2楼-- · 2019-02-11 13:26

The approach is right, but there are few problems.

You've created the FXMLLoader named fxmlLoader but then you call FXMLLoader.load() which is static method and is not connected with instance you've created before.

Also using static method to intercommunication is not very good (imagine you'll want to have several processes). Better store myController to a field and call it in the stop() method.

Even better would be to add corresponding utility method to Controller and call it from main app, because main app doesn't seem to use Process itself.


Here goes short app to demonstrate all described:

public class DoTextAreaLog extends Application {

    private LoggController controller;

    @Override
    public void start(Stage stage) throws IOException{
            FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("logg.fxml"));
            VBox root = (VBox)fxmlLoader.load();
            controller = (LoggController) fxmlLoader.getController();

            stage.setScene(new Scene(root, 400, 300));
            stage.show();
    }

    @Override
    public void stop() throws Exception {
        super.stop();
        controller.destroy();
    }

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

Controller:

public class LoggController implements Initializable {

    @FXML private TextArea textarea;

    @FXML private void onAction(ActionEvent event) {
        destroy();
    }

    private Process p;

    public void destroy() {
        if (p != null) {
            p.destroy();
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        try {
            p = new ProcessBuilder("ping", "stackoverflow.com", "-n", "100").start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        try (BufferedReader bri = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
                            String line;

                            while ((line = bri.readLine()) != null) {
                                log(line);
                            }
                        }
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }).start();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void log(final String st) {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                textarea.setText(st + "\n" + textarea.getText());
            }
        });
    }
}

logg.fxml:

<VBox id="root" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml"  fx:controller="fxml.LoggController">
    <TextArea fx:id="textarea"/>
    <Button text="Stop The Madness!" onAction="#onAction"/>
</VBox>
查看更多
登录 后发表回答