I have a Runnable that reads Console output from an externally called exe (see below) and writes it to both a log file and a JTextArea.
But my Runnable doesn\'t show
You could try the same logic with a SwingWorker instead. You can extend this class, instead of implementing runnable. It could take your Text area as a paramater, and you can publish the data, without having to deal with the SwingUtils.invokeLater, which is guiltily easier...
Try:
public class execute extends javax.swing.SwingWorker {
String line;
Process p;
JTextArea jta;
File f = new File( properties.prop.getProperty( "LOG_FILE_DIR" ) + "\\PartGen.log");
public execute ( Process process , JTextArea jta ) {
p = process;
this.jta = jta;
}
//implements a method in the swingworker
public void doInBackground() throws Exception {
//Read Process Stream Output and write to LOG file
BufferedReader is = new BufferedReader( new InputStreamReader(p.getInputStream()));
while ( (line = is.readLine()) != null ) {
osfile.writeline(line, f);
publish(new String(line + "\n"));
}
System.out.flush();
return null;
}
//This will happen on the UI Thread.
public void process(List lines){
for(Object o : lines){
jta.append((String)o);
}
}
public void done(){
try{
get();
//You will get here if everything was OK. So show a popup or something to signal done.
}catch(Exception ex){
//this is where your IO Exception will surface, should you have one.
}
}
}
Also, in your calling code, which I assume is in your ui somewhere:
Process p = new ProcessBuilder(command).start();
execute ex = new execute( p , yourTextArea);
ex.execute();
I didnt attempt to compile this, so you may have to check against the API, but hopefully it will give you a gist of what to do.
The problem was not that the thread wasn't capturing the data, it was the JTextArea just not refreshing. repaint()
, revalidate()
, and updateUI()
did not refresh the JTextArea, but the following did:
Example.ta.update(Example.ta.getGraphics());
The problem in this case is with the waitFor:
p.waitFor();
This causes the Button Action Listener to wait on that point until the process is completed.
Maybe it's because you don't respect Swing's threading policy. All accesses to swing components must be done in the event dispatch thread. Your runnable should thus use SwingUtilities.invokeLater
to update the text area in the EDT, rather than in your separate thread.
EDIT : as alf mentions in his comment: JTextArea.append
is thread-safe, so it's not absolutely needed here. I would still do it, though, because if the append to a text area was replaced or complemented by any other Swing interaction, it wouldn't be thread-safe anymore.
It could also be that the external process doesn't send any newline character, which makes readLine
block until one is found or the end of communication is reached.
Just to help the miner - below's a complete minimalistic (left out everything not absolutely necessary) example that indeed works in my context: each line appears in the textArea as read. It's basically using the SwingWorker as suggested by Justin and re-arranged thingies a bit for clarity.
public class ProcessExample {
public static class ProcessWorker extends SwingWorker<Void, String> {
private JTextArea ta;
private List<String> process;
public ProcessWorker(List<String> command, JTextArea ta) {
this.process = command;
this.ta = ta;
}
@Override
protected Void doInBackground() throws Exception {
Process p = new ProcessBuilder(process).start();
// Read Process Stream Output and write to LOG file
BufferedReader is = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String line;
while ((line = is.readLine()) != null) {
publish(line);
}
is.close();
return null;
}
@Override
protected void process(List<String> chunks) {
for (String string : chunks) {
ta.append(string + "\n");
}
}
}
private void startProcess(JTextArea ta) {
ArrayList<String> command = new ArrayList<String>();
command.add("ping");
command.add("127.0.0.1");
new ProcessWorker(command, ta).execute();
}
private JComponent getContent() {
JPanel main = new JPanel(new BorderLayout());
final JTextArea ta = new JTextArea(20, 60);
main.add(new JScrollPane(ta));
Action action = new AbstractAction("Start!") {
@Override
public void actionPerformed(ActionEvent e) {
startProcess(ta);
}
};
main.add(new JButton(action), BorderLayout.SOUTH);
return main;
}
public static void main(String args[]) throws IOException {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ProcessExample().getContent());
frame.pack();
frame.setVisible(true);
}
});
}
}