如何让这个方法在循环中更新GUI?

我正在制作一个运行几个cmd命令的程序(USMT和文件传输)

它运行正常,但我只在完成操作后才从文本框中的cmd获取最后一行。 我想让它打印cmd实时输出的内容。

public void load() throws IOException { 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; } cOut.setText(line); System.out.println(line); } } 

原因…

问题的根本原因是您阻止了事件调度线程,这阻止了UI在命令执行之后才更新。

Swing是一个单线程框架,这意味着您不应该在EDT的上下文中执行阻塞或长时间运行的代码。 Swing也不是线程安全的,这意味着你永远不应该从EDT的上下文之外修改UI的状态。

有关更多详细信息,请参阅Swing中的并发

解…

要解决此问题,您有两个基本选项。 您可以使用Thread ,但是您负责确保UI的任何和所有更新都与EDT的上下文同步,或者您可以使用SwingWorker ,例如……

 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, String> { private JTextArea ta; private List commands; public CommandWorker(JTextArea ta, List commands) { this.ta = ta; this.commands = commands; } public CommandWorker(JTextArea ta, String... commands) { this(ta, Arrays.asList(commands)); } @Override protected List doInBackground() throws Exception { List 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 chunks) { for (String text : chunks) { ta.append(text); ta.append("\n"); } } } } 

有关更多详细信息,请参阅Worker Threads和SwingWorker

目前,您正在使用readLine()读取命令输出,然后直接将其放入setText()

实时更新

为了使代码实时更新,我们定义了一个新的Thread并使用该线程通过套接字读取OutputStream

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

上面,我们定义了一个用于读取行的新线程。

输出所有行

有时,您需要将命令在屏幕上打印的所有行都放在屏幕上,这很容易使用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); } 

在将其写入屏幕之前,上面使用StringBuilder临时存储完成的结果。