I\'m making a program that runs a few cmd commands (USMT and file transfer)
It\'s working fine, but I only get the last line from the cmd in my text box and only af
At the moment, you are reading the command output using readLine()
, and then you are directly putting it into setText()
.
To make the code update it in real time, we are defining a new Thread and use that thread to read over the OutputStream
over the socket:
public void load() throws IOException {
Thread t = new Thread(() -> {
try {
ProcessBuilder builder = new ProcessBuilder(
"cmd.exe", "/c", "cd \"C:\\usmt\" && loadstate.bat");
builder.redirectErrorStream(true);
Process p = builder.start();
BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while (true) {
line = r.readLine();
if (line == null) {
break;
}
String l = line;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
cOut.setText(l);
}
});
System.out.println(line);
}
} catch (IOException ex) {
ex.printStackTrace(); //Add a better error handling in your app
}
});
t.start();
}
Above, we define a new thread that is used to read the lines.
Sometimes, you need to put all the lines the command is printing on screen, this is easy to do using a StringBuilder:
String line;
StringBuilder total = new StringBuilder();
while (true) {
line = r.readLine();
if (line == null) {
break;
}
total.append(line).append('\n');
cOut.setText(total.toString());
System.out.println(line);
}
The above uses a StringBuilder to temporary store the finished result, before writing it to the screen.
The basic cause of your problem is the fact that you are blocking the Event Dispatching Thread, this is preventing the UI from been updated until AFTER the command has executed.
Swing is a single threaded framework, meaning that you shouldn't execute blocking or long running code from within the context of the EDT. Swing is also NOT thread safe, meaning that you should never modify the state of the UI from outside of the context of the EDT.
See Concurrency in Swing for more details
To solve this you have two basic options. You could use a Thread
, but then you become responsible for ensuring that any and all updates to the UI are synchronised to the context of the EDT, or you could use a SwingWorker
, for example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Runner {
public static void main(String[] args) {
new Runner();
}
public Runner() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTextArea ta;
public TestPane() {
setLayout(new BorderLayout());
ta = new JTextArea(25, 80);
add(new JScrollPane(ta));
JButton execute = new JButton("Make it so");
execute.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
execute.setEnabled(false);
CommandWorker worker = new CommandWorker(ta, "cmd.exe", "/c", "cd \"C:\\usmt\" && loadstate.bat");
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (evt.getPropertyName()) {
case "state":
SwingWorker work = (SwingWorker) evt.getSource();
switch (worker.getState()) {
case DONE: {
try {
worker.get();
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();;
JOptionPane.showMessageDialog(TestPane.this, "Execution of command failed: " + ex.getMessage());
} finally {
execute.setEnabled(true);
}
}
break;
}
break;
}
}
});
worker.execute();
}
});
add(execute, BorderLayout.SOUTH);
}
}
public static class CommandWorker extends SwingWorker<List<String>, String> {
private JTextArea ta;
private List<String> commands;
public CommandWorker(JTextArea ta, List<String> commands) {
this.ta = ta;
this.commands = commands;
}
public CommandWorker(JTextArea ta, String... commands) {
this(ta, Arrays.asList(commands));
}
@Override
protected List<String> doInBackground() throws Exception {
List<String> output = new ArrayList<>(25);
ProcessBuilder builder = new ProcessBuilder(commands);
builder.redirectErrorStream(true);
Process p = builder.start();
try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line = null;
while ((line = r.readLine()) != null) {
output.add(line);
publish(line);
}
}
return output;
}
@Override
protected void process(List<String> chunks) {
for (String text : chunks) {
ta.append(text);
ta.append("\n");
}
}
}
}
See Worker Threads and SwingWorker for more details