【问题标题】:Update jLabel content from the output of shell script从 shell 脚本的输出中更新 jLabel 内容
【发布时间】:2015-06-12 07:43:50
【问题描述】:

我在 jFrame 中有一个 jLabel,它必须从 shell 脚本的输出中更新其内容。 shell 脚本的执行是由另一个 jFrame 中的线程完成的,我将输出存储在 StringBuilder(公共和静态)中:

p = Runtime.getRuntime().exec("testpad -i -c"+can+" -n"+pad+" "+pathFile);
            final InputStream inStream = p.getInputStream();

            Thread uiThread = new Thread("UIHandler") {
                @Override
                public void run() {
                  InputStreamReader reader = new InputStreamReader(inStream);
                  Scanner scan = new Scanner(reader);
                  prec=null;

                  while (scan.hasNextLine()) {
                    prec=scan.nextLine();
                    System.out.println(prec);
                    sbuff.append(prec);
                    sbuff.append('\n');

                  }
               }
            };
            uiThread.start();

当显示 jPannel 时,我会更新 jLabel(jPannel 中的内容):

private void jPanel1ComponentShown(java.awt.event.ComponentEvent evt) {                                       
    // TODO add your handling code here:
    jLabel2.setText(inizio.sbuff.toString());

}

我认为这是一个竞争条件问题,但我放了一个 Thread.sleep() 有很多秒,但 jLabel 的内容没有更新。 我这样做的例子:

当我按下 jButton 时,shell 脚本会在矩形中以红色打印输出,然后它会打开一个带有 jLabel 的新 jFrame,它应该更新但它不会改变。我哪里错了?谢谢。

【问题讨论】:

  • 考虑提供一个runnable example 来证明您的问题。这不是代码转储,而是您正在做的事情的一个例子,它突出了您遇到的问题。这将减少混乱并获得更好的响应
  • 我也会考虑使用ProcessBuilderSwingWorker 而不是Runtime.execThread
  • 我放了一个执行的例子。感谢您的点击。

标签: java multithreading swing shell


【解决方案1】:

不可能 100%,因为您没有提供任何证据来支持代码的执行方式,但我假设当您启动 Thread 时,您也会打开第二帧。

这可能是个坏主意。相反,您可以使用SwingWorker,这将允许您在后台执行脚本,从而释放 UI 线程。

SwingWorker 完成后,您将创建并显示将脚本结果传递给它的第二个窗口...

public class ExecuteWorker extends SwingWorker<String, String> {

    private String can, pad, pathfile;

    public ExecuteWorker(String can, String pad, String pathfile) {
        this.can = can;
        this.pad = pad;
        this.pathfile = pathfile;
    }

    @Override
    protected String doInBackground() throws Exception {
        ProcessBuilder pb = new ProcessBuilder("testpad", "-i", "-c" + can, "-n" + pad, pathfile);
        pb.redirectErrorStream(true);

        StringJoiner sj = new StringJoiner("\n");
        Process p = pb.start();
        try (InputStreamReader reader = new InputStreamReader(p.getInputStream());
                Scanner scan = new Scanner(reader)) {

            while (scan.hasNextLine()) {
                String text = scan.nextLine();
                System.out.println(text);
                sj.add(text);

            }

        }
        return sj.toString();
    }

    @Override
    protected void done() {
        try {
            String text = get();
            // Create and show second UI here...
        } catch (Exception e) {
        }
    }

}

查看Concurrency in SwingWorker Threads and SwingWorker 了解更多详情。我还想看看The Use of Multiple JFrames, Good/Bad Practice? 并考虑改用CardLayout,有关更多详细信息,请参阅How to Use CardLayout

另一种方法可能是使用生产者-消费者模式,其中SwingWorker 是生产者,我们使用interface 作为消费者的合同。这样,您的 UI 可以implement 消费者合同,并在有新内容可用时收到通知

public interface Consumer {
    public void consume(String text);
}

public class ExecuteWorker extends SwingWorker<String, String> {

    private String can, pad, pathfile;
    private Consumer consumer;

    public ExecuteWorker(Consumer consumer, String can, String pad, String pathfile) {
        this.can = can;
        this.pad = pad;
        this.pathfile = pathfile;
    }

    @Override
    protected void process(List<String> chunks) {
        for (String text : chunks) {
            consumer.consume(text);
        }
    }

    @Override
    protected String doInBackground() throws Exception {
        ProcessBuilder pb = new ProcessBuilder("testpad", "-i", "-c" + can, "-n" + pad, pathfile);
        pb.redirectErrorStream(true);

        StringJoiner sj = new StringJoiner("\n");
        Process p = pb.start();
        try (InputStreamReader reader = new InputStreamReader(p.getInputStream());
                Scanner scan = new Scanner(reader)) {

            while (scan.hasNextLine()) {
                String text = scan.nextLine();
                System.out.println(text);
                publish(text);
                sj.add(text);

            }

        }
        return sj.toString();
    }

}

这意味着您可以同时启动SwingWorker 并设置新的 UI,这也意味着您无需等待进程退出即可从中获取输出。

在上面的例子中,我们使用了 worker 的 publish/process 功能来同步 UI 线程的更新,从而可以安全地更新 UI 组件。

哦,除非您将输出文本包装在 HTML 中,否则 JLabel 可能不是您的最佳选择,也许可以考虑使用 JTextArea 代替

【讨论】:

  • 感谢您的出色回复,并为我的无能感到抱歉。线程在第一帧开始时也开始菜单,其中有图片中的 jbutton“jbutton5”。只有当我按下 jbutton5 时,它才会打开另一个 jframe,而现在我已经放置了一个 JTextArea。在这个 JTextArea 中,我必须将启动的脚本的输出。还有一件事:我使用了一个线程,因为该进程永远不会停止它并且正在无限运行。
  • 那么我不会依赖访问线程之间的变量,而是使用类似于第二个示例的东西
  • 如何返回sj.toString();?带有 scan.hasNextLine() 的 while 总是在监听等待更多输入?
  • 通过publish/process 功能和Consumer
【解决方案2】:

看问题描述,我的猜测是你在得到结果之前尝试更新标签。

与其更新组件显示回调中的文本,不如在后台处理完成后尝试显式设置文本。

您的方法还有其他问题,例如不使用 swing worker 执行后台任务,尝试在 JLabel 中显示多行文本(JTextArea 将更适合于此)。

【讨论】:

  • 谢谢,现在我放了一个 JTextArea。线程完成处理后,如何更新 JTextArea?我尝试了类似“if(prec.eq​​uals(null))”的方法,但不起作用。它什么时候停止了变量 Scanner?
  • 我该怎么做:“与其更新组件显示回调中的文本,不如在后台处理完成后尝试显式设置文本。”。我正在尝试这样做:“if(prec.eq​​uals(null)){latch.countDown();}”但不起作用。不幸的是,该软件处于删除线程的高级状态。
  • 有多种方法可以等待线程完成。一种方法是将 SwingUtilitities.invokeLater() 作为线程中的最后一条语句。您可以从传递给 invokeLater 的 runnable 更新文本区域。您可以在各种 java 教程网站上获得有关如何使用 invokeLater 的更多详细信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-26
相关资源
最近更新 更多