4

Newbie question about JavaFX that I haven't been able to answer, despite knowing it must be pretty simple to do and not finding any resources on it anywhere I've looked (tutorials, many of the Oracle online docs, articles, the well-known JavaFX bloggers, etc.)

I'm developing a command line (script) running application and I have successfully gotten output (via ProcessBuilder) from the script that I can display in an ongoing manner, as things happen on the command line. That is, I can do System.out.println(line); all day long, showing the output in the console, which simply returns output from an input stream returned by the 'myProcess' that's running, created like this:

BufferedReader bri = new BufferedReader(new InputStreamReader(myProcess.getInputStream()))

So I am able to see all the output coming back from the script.

I'd like to set-up a JavaFX TextArea or ScrollPane or, not sure what, to display this output text (there's a lot of it, like several thousand lines) as an ongoing 'progress' of what's taking place in the script, as it happens. I have a Scene, I have buttons and get input from this scene to start the script running, but now I'd like to show the result of clicking the button "RUN THIS SCRIPT", so to speak.

I assume I need to create a TextArea as described here or perhaps a TextBuilder would be useful to begin making it. Not sure.

I need a bit of help in how to setup the binding or auto-scroll/auto-update part of this.

Can someone provide me a place to start, to do this with JavaFX? I'd rather not use Swing.

(I'm using JavaFX 2.2, JDK 1.7u7, all the latest stuff, and yes, this is an FXML app--so doing it that way would be preferred.)

UPDATE: Sergey Grinev's answer was very helpful in the binding part. But here is some more detail on what I mean when I ask for "a bit of help in how to setup" -- basically, I need to return control to the main Scene to allow the user to Cancel the script, or to otherwise monitor what's going on. So I'd like to "spawn" the process that runs that script (that is, have some kind of 'free running process'), but still get the output from it. (I wasn't very clear on that in my initial question.)

The technique I'm using here (see below) is to do a waitFor on the process, but of course this means the dialog/Scene is 'hung' while the script executes. I'd like to gain control back, but how do I pass the 'p' (Process) to some other controller piece (or alternatively, simply kick off that other process passing in the parameters to start the script and have it start the script) that will then do the auto-update, via the binding Sergey Grinev mentions--without 'hanging' the Scene/window? Also: Can I then 'stop' this other process if the user requests it?

Here is my current code ('waits' while script--which takes 20-40 min to run!--completes; this is not what I want, I'd like control returned to the user):

public class MyController implements Initializable {  
  @FXML
  private void handleRunScript(ActionEvent event) throws IOException {

    ProcessBuilder pb = new ProcessBuilder("myscript.sh", "arg1", "arg2", ...);

    Process p = pb.start();
    try {
      BufferedReader bri = new BufferedReader
        (new InputStreamReader(p.getInputStream()));
      String line;

      while ((line = bri.readLine()) != null) {
        System.out.println(line);
        textAreaRight.setText(line);
      }
      bri.close();
      p.waitFor();
    }
    catch (Exception err) {
      err.printStackTrace();
    }    
  }

  @FXML
  private void handleCancel(ActionEvent event) {
    doSomethingDifferent();
  }
}
likethesky
  • 846
  • 3
  • 12
  • 28

1 Answers1

5
  1. To log strings you can use TextArea
  2. To make it asynchronious you need to make a separate thread for output reader.

public class DoTextAreaLog extends Application {

    TextArea log = new TextArea();
    Process p;

    @Override
    public void start(Stage stage) {
        try {
            ProcessBuilder pb = new ProcessBuilder("ping", "stackoverflow.com", "-n", "100");

            p = pb.start();

            // this thread will read from process without blocking an application
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //try-with-resources from jdk7, change it back if you use older jdk
                        try (BufferedReader bri = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
                            String line;

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

            stage.setScene(new Scene(new Group(log), 400, 300));
            stage.show();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }

    @Override
    public void stop() throws Exception {
        super.stop();
        // this called on fx app close, you may call it in user action handler
        if (p!=null ) {
            p.destroy();
        }
    }

    private void log(final String st) {
        // we can access fx objects only from fx thread
        // so we need to wrap log access into Platform#runLater
        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                log.setText(st + "\n" + log.getText());
            }
        });
    }

    public static void main(String[] args) {
        launch();
    }
}
Sergey Grinev
  • 34,078
  • 10
  • 128
  • 141
  • Thanks Sergey. I added an update to my question, with a few more specifics. I'm afraid I'm not totally clear on how to keep the TextArea 'updating' while the script runs, while also giving control back to the user--ideally, with the ability for the user to stop the script--end the process that's running it--if they want to. I think the key is: I need to be able to communicate between these two, by passing the process id, or something, and being able to 'kill' the process if necessary. Thanks again. – likethesky Sep 07 '12 at 19:03
  • see update. For communication and termination ask another question, please. Or search site, it's a common topic. – Sergey Grinev Sep 07 '12 at 20:56
  • Beautiful. Thanks Sergey! Yes, I considered asking a separate question when my update got a bit long... I'll do that if I need pointers on the interprocess communication/termination (after googling a bit here & elsewhere first, of course). Thanks again! Once I get it working I'll accept your answer--it already has my upvote. Cheers. – likethesky Sep 07 '12 at 21:00
  • I have it all working, except the `stop();` in my FXML app. I've been googling around and can't seem to figure out how to interact between the Controller class and the Application class. Obviously there are plenty of methods without using FXML, but few (none, that I can find) discussing it with FXML, though I suppose the principles should be similar. Here is my new question: http://stackoverflow.com/questions/12361600/javafx-fxml-communication-between-application-and-controller-classes In the meantime--thanks again for your help. I've accepted your answer. – likethesky Sep 11 '12 at 01:38