0

I want to change a JLabel dynamically with each new string produced by a simple Ping but can't figure out how to replace the existing string on the JLabel with each new string coming in as the Ping is executing. Below is what I have so far. So far it replaces the text of the JLabel but only after the Ping has finished executing.

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import java.io.*;

public class Ping extends JFrame implements ActionListener{

private JButton runButton = new JButton("Run");
private JLabel pingResult = new JLabel("Result"); 
private String results;

public Ping(){
    runButton.addActionListener(this);

    add(pingResult, BorderLayout.CENTER);
    add(runButton, BorderLayout.NORTH);

}

//Action Listener
public void actionPerformed(ActionEvent e)
{
    String buttonString = e.getActionCommand( );

    if (buttonString.equals("Run"))
    {
        //Execute Ping
        try {
            String line;
            Process p = Runtime.getRuntime().exec("/sbin/ping -c 4 www.google.com");
            BufferedReader input = new BufferedReader(
                new InputStreamReader(p.getInputStream()));

            while ((line = input.readLine()) != null) {
                results += line + "\n";
                pingResult.setText(results);
                //System.out.println(line);
            }

            input.close();
        }catch (Exception err){
            err.printStackTrace();
        }

    }else{
        System.exit(0);
    }
}

public static void main(String[] args) {
    Ping sp = new Ping();
    sp.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    sp.setSize(400, 400);
    sp.setVisible(true);
    sp.setLayout(new BorderLayout());
}

}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
user1524652
  • 41
  • 1
  • 4

3 Answers3

3

Call setText on the event dispatching thread. Use SwingUtils.invokeLater for that. Do not execute long lasting operations from the EDT. This will cause your app to freeze, as you are experiencing.

sorencito
  • 2,517
  • 20
  • 21
1

Your code freezes EDT (Event Dispatch Thread). That's why JLabel text is updated after ping is finished. You should avoid doing that. Running long lasting operations in different thread and updating it using SwingUtils.invokeLater is one solution. Another is usage of SwingWorker class. You can find tutorial here.

Below is an example with first option. I used JTextArea instead of JLabel.

And one more thing you should use ProcessBuilder instead of Runtime.exec(), here is nice article why.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class PingExample {
  private static final Logger logger = Logger.getLogger(PingExample.class.getName());

  public static void main(String[] args) {
    PingExample p = new PingExample();
    p.run();
  }

  private void run() {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        JFrame frame = new PingFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
      }
    });
  }

  private class PingFrame extends JFrame {
    private static final long serialVersionUID = 1L;

    private JTextArea textArea;

    private PingFrame() {
      super.setName("Ping Frame");
      this.addComponents();
      super.setSize(400, 240);
    }

    private void addComponents() {
      super.setLayout(new BorderLayout());

      this.textArea = new JTextArea(10, 0);
      JScrollPane scrollPane = new JScrollPane(this.textArea);
      super.add(scrollPane, BorderLayout.NORTH);

      JButton button = new JButton("Run");
      button.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          new Thread(new Ping(PingFrame.this.textArea)).start();
        }
      });
      super.add(button, BorderLayout.SOUTH);
    }

    private class Ping implements Runnable {
      private JTextArea textArea;

      private Ping(JTextArea textArea) {
        this.textArea = textArea;
      }

      @Override
      public void run() {
        try {
          List<String> commands = new ArrayList<>(10);
          commands.add("ping");
          commands.add("www.google.com");

          ProcessBuilder builder = new ProcessBuilder();
          builder.command(commands);
          Process process = builder.start();

          try (
              BufferedReader br = new BufferedReader(
              new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
            String line = br.readLine();
            while (null != line) {
              final String text = line;
              SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                  Ping.this.textArea.append(text);
                  Ping.this.textArea.append("\n");
                }
              });
              line = br.readLine();
            }
          }

          process.waitFor();
          process.destroy();
        } catch (IOException | InterruptedException x) {
          logger.log(Level.SEVERE, "Error", x);
        }
      }
    }
  }

}

mleczey
  • 513
  • 7
  • 16
  • Also as a side note, its recommended to at least change the mouse pointer to "busy" for operations that may last longer than 2 seconds while a progress bar would be good for even longer operations. – Sayo Oladeji Dec 30 '12 at 11:14
0

Exactly, it will update it only after the ping finished. If you want to do it before it finished, you need to use a different Thread for it.

Abraham
  • 603
  • 7
  • 19