Update jLabel content from the output of shell scr

2019-03-02 08:12发布

I have in a jFrame a jLabel that must update its content from the output of shell script. The execution of the shell script is made from a thread in another jFrame in which I store the output in a StringBuilder (public and static):

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

I update the jLabel (content in a jPannel) when the jPannel is showed:

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

}

I think it is a race condition trouble but I put a Thread.sleep() with many second but the conten of jLabel don't update. I am doing this how example:

enter image description here

When I push the jButton the shell script print the output in the rectangle in red , then it opens a new jFrame with the jLabel that it should update but its don't change. Where am I wrong? Thanks.

2条回答
够拽才男人
2楼-- · 2019-03-02 08:46

Looking at the problem description, my guess is that you are trying to update the label before getting the results.

Rather than updating text in component shown callback, try explicitly setting the text after background processing is complete.

There are other problems with your approach like not using swing worker for background tasks, trying to display multiline text in a JLabel (JTextArea will be better suited for this).

查看更多
一夜七次
3楼-- · 2019-03-02 09:01

It's impossible to 100% as you've not provided any evidence to support how the code is executed, but I'm assuming that when you start the Thread, you also open the second frame.

This is probably a bad idea. Instead, you could use a SwingWorker which will allow you to execute the script in the background, freeing up the UI thread.

When the SwingWorker completes, you would then create and show the second window passing the result of the script to it...

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) {
        }
    }

}

Have a look at Concurrency in Swing and Worker Threads and SwingWorker for more details. I'd also have a look at The Use of Multiple JFrames, Good/Bad Practice? and consider using a CardLayout instead, see How to Use CardLayout for more details

A alternative approach might be to use a Producer-Consumer pattern, where the SwingWorker is the producer and we use a interface which acts a contract for the consumer. In this way, you UI could implement the consumer contract and be notified when new content was made available

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

}

This means you could start the SwingWorker AND setup the new UI at the same time, it also means you don't need to wait for the process to exit before getting output from it.

In the above example, we use the publish/process functionality of the worker to synchronise updates to the UI thread, making it safe to update the UI components from.

Oh, and unless you wrap the output text in HTML, JLabel is probably not your best bet, maybe consider using a JTextArea instead

查看更多
登录 后发表回答