1

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.

Local Hero
  • 493
  • 2
  • 7
  • 20
  • Consider providing a [runnable example](https://stackoverflow.com/help/mcve) which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses – MadProgrammer Jun 12 '15 at 07:47
  • I'd also consider using a `ProcessBuilder` and a [`SwingWorker`](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html) instead of `Runtime.exec` and `Thread` – MadProgrammer Jun 12 '15 at 07:48
  • I have put an example of execution. Thanks for the hit. – Local Hero Jun 12 '15 at 08:01

2 Answers2

1

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

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks for the great reply and sorry for my incompetence. The thread starts in a first frame when starts also the menu where there is the jbutton "jbutton5" in the picture. Only when I push the jbutton5 it is opening another jframe when now I have putted a JTextArea. In this JTextArea I must put the output of the script launched. Yet another thing: I have used a thread because the process never stop it and is running to infinite. – Local Hero Jun 12 '15 at 09:25
  • Then I wouldn't rely on accessing a variable between threads, instead, use something like the second example – MadProgrammer Jun 12 '15 at 09:34
  • How does it do to return sj.toString();? The while with the scan.hasNextLine() just always listening waiting for more input? – Local Hero Jun 12 '15 at 09:37
  • Via the `publish`/`process` functionality and the `Consumer` – MadProgrammer Jun 12 '15 at 09:41
1

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).

Ashwinee K Jha
  • 9,187
  • 2
  • 25
  • 19
  • Thanks, now I have putted a JTextArea. How can I do to update the JTextArea when the thread has finished the processing? I try something like "if(prec.equals(null))" but doesn't work. When did it stop the variable Scanner? – Local Hero Jun 12 '15 at 10:15
  • How can I do this: "Rather than updating text in component shown callback, try explicitly setting the text after background processing is complete.". I am trying to do this: "if(prec.equals(null)){ latch.countDown(); }" but don't work. Unfortunately, the software is in a advanced state to remove the thread. – Local Hero Jun 12 '15 at 10:58
  • There are multiple ways to wait for the thread to finish. One way can be to put SwingUtilitities.invokeLater() as last statement in your thread. You can update the text area from the runnable passed to the invokeLater. You can get more details about how to use invokeLater at various java tutorial sites. – Ashwinee K Jha Jun 12 '15 at 14:50