Текст автоматического обновления прокрутки JavaFX

Новый вопрос о JavaFX, на который я не смог ответить, несмотря на то, что он знает, что это должно быть довольно просто, и не находить какие-либо ресурсы на нем в любом месте, где я смотрел (учебники, многие онлайн-документы Oracle, статьи, известных блоггеров JavaFX и т.д.)

Я разрабатываю приложение с командной строкой (script), и я успешно получил выход (через ProcessBuilder) из script, который я могу отображать в непрерывном режиме, как это происходит в командной строке. То есть, я могу сделать System.out.println(line); весь день, показывая вывод в консоли, который просто возвращает вывод из потока ввода, возвращаемого "myProcess", который работает, созданный следующим образом:

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

Итак, я могу видеть, что все выходные данные возвращаются из script.

Я хотел бы настроить JavaFX TextArea или ScrollPane или, не уверен, что, чтобы отобразить этот выходной текст (там много его, как несколько тысяч строк), как постоянный "прогресс" того, что происходит в script, как это бывает. У меня есть сцена, у меня есть кнопки и вход с этой сцены, чтобы запустить script, но теперь я хотел бы показать результат нажатия кнопки "RUN THIS SCRIPT", так сказать.

Я предполагаю, что мне нужно создать TextArea, как описано здесь, или, возможно, TextBuilder было бы полезно начать его создание. Не уверен.

Мне нужна небольшая помощь в том, как настроить привязку или автопрокрутку/автообновление этой части.

Может ли кто-нибудь предоставить мне место для начала, чтобы сделать это с помощью JavaFX? Я бы предпочел не использовать Swing.

(Я использую JavaFX 2.2, JDK 1.7u7, все последние вещи, и да, это приложение FXML, поэтому делать это было бы предпочтительнее.)

ОБНОВЛЕНИЕ: ответ Сергея Гринева был очень полезен в обязательной части. Но вот более подробная информация о том, что я имею в виду, когда я прошу "немного помочь в настройке" - в основном, мне нужно вернуть управление основной сцене, чтобы пользователь мог отменить script, или в противном случае следить за тем, что происходит. Поэтому я хотел бы "запустить" процесс, который запускает этот script (то есть, имеет какой-то "процесс свободного запуска" ), но все равно получает от него результат. (В моем первоначальном вопросе я был не очень ясен).

Техника, которую я использую здесь (см. ниже), - это делать waitFor в процессе, но, конечно, это означает, что диалог /Scene "зависает", пока выполняется script. Я бы хотел получить контроль, но как передать "p" ( "Процесс" ) на какую-либо другую часть контроллера (или, наоборот, просто запустить этот другой процесс, проходящий в параметрах, чтобы запустить script и запустить его script), который затем выполнит автоматическое обновление, используя привязку Сергея Гринева, - без "висячего" окна "Сцена/окно"? Также: Могу ли я затем остановить этот другой процесс, если пользователь его запросит?

Вот мой текущий код ('waits' while script - который занимает 20-40 минут для запуска! - завершает, это не то, что я хочу, я бы хотел, чтобы элемент управления вернулся пользователю):

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();
  }
}

Ответ 1

  • Чтобы записывать строки, вы можете использовать TextArea
  • Чтобы сделать его асинхронным, вам нужно создать отдельный поток для устройства вывода.

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();
    }
}