过程输出仅在过程完成后才可用

时间:2011-11-10 15:34:34

标签: java swing runnable

我有一个Runnable,它从外部调用的exe读取Console输出(见下文),并将其写入日志文件和JTextArea。

但是我的Runnable在exe完全完成之前不会在JTextArea中显示控制台输出。如何在打印控制台输出时将其打印出来?

简短简洁代码示例如下:

//主

import java.awt.*;
import java.io.IOException;
import javax.swing.*;

public class Example extends JFrame {

    private static final long serialVersionUID = 1L; 

    public static int maxX, maxY;

    public static JTextArea ta = new JTextArea(20, 60);//For LOG display window

    public static void main(String args[] ) throws IOException
    {   
        new Example();
    }

    public Example() {

        this.setTitle("Example");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //MAIN Panel
        final JPanel main = new JPanel();

        JButton RunButton = button.run(main);
        main.add(RunButton);

        Container container = getContentPane();


        container.add(main);
        this.pack();
        this.setVisible(true);
    }


}

//按钮动作侦听器

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


public class button {

    public static JButton run( final JPanel parent ) {
        JButton RunButton = new JButton();      
        RunButton.setText("Start!");

        RunButton.addActionListener(
        new ActionListener()
        {
            public void actionPerformed( ActionEvent event)
            {
                try
                {

                    //Set up LOG Display    
                    JDialog dialog = new JDialog((JFrame)null, "Working...");
                    JPanel temp_panel = new JPanel();
                    temp_panel.add(new JScrollPane(Example.ta));
                    dialog.getContentPane().add(temp_panel);
                    dialog.pack();
                    dialog.setVisible(true);

                    //Build the Command
                    ArrayList<String> command = new ArrayList<String>();
                    command.add("ping");
                    command.add("127.0.0.1");

                    //Start the process
                    Process p = new ProcessBuilder(command).start();

                    //Starts LOG display capture in separate thread 
                    SwingUtilities.invokeLater(new execute(p));

                    //Wait for call to complete
                    p.waitFor();
                }
                catch(Exception err)
                {
                    JOptionPane.showMessageDialog( parent, "Error Executing Run!", "Warning", JOptionPane.ERROR_MESSAGE );  
                }

            }//end ActionPerformed
        });
        return RunButton;
    }
}

//可运行

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class execute implements Runnable {  
    String line;
    Process p; 

    public execute ( Process process ) {
        p = process;
    }

    public void run() {
        try {
            //Read Process Stream Output and write to LOG file
            BufferedReader is = new BufferedReader( new InputStreamReader(p.getInputStream()));

            while ( (line = is.readLine()) != null ) {
                Example.ta.append(line + "\n");
            }   
            System.out.flush();     
        } catch(Exception ex) { ex.printStackTrace(); }  

    }
}

5 个答案:

答案 0 :(得分:3)

也许是因为你不尊重Swing's threading policy。所有对swing组件的访问都必须在事件派发线程中完成。因此,您的runnable应该使用SwingUtilities.invokeLater来更新EDT中的文本区域,而不是在单独的线程中。

编辑:正如他在评论中提到的那样:JTextArea.append是线程安全的,所以这里并不是绝对需要的。不过,我仍然会这样做,因为如果文本区域的附加被任何其他Swing交互替换或补充,那么它将不再是线程安全的。

也可能是外部进程没有发送任何换行符,这使得readLine阻塞直到找到一个或者到达通信结束。

答案 1 :(得分:3)

只是为了帮助矿工 - 下面是一个完整的简约(省略了一切并非绝对必要)的例子,确实在我的上下文中有效:每行在textArea中显示为read。它基本上使用了Justin建议的SwingWorker,并为了清晰起见重新安排了一些东西。

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

            }
        });
    }

}

答案 2 :(得分:1)

您可以使用SwingWorker尝试相同的逻辑。您可以扩展此类,而不是实现runnable。它可以将你的文本区域作为参数,你可以发布数据,而不必处理SwingUtils.invokeLater,这很容易...

尝试:

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

另外,在你的调用代码中,我假设它位于你的ui某处:

    Process p = new ProcessBuilder(command).start();
    execute ex = new execute( p , yourTextArea);
    ex.execute();

我没有尝试编译这个,所以你可能需要检查API,但希望它会给你一个如何做的要点。

答案 3 :(得分:0)

问题不在于线程没有捕获数据,而是JTextArea只是没有刷新。 repaint()revalidate()updateUI()未刷新JTextArea,但以下内容确实如此:

Example.ta.update(Example.ta.getGraphics()); 

答案 4 :(得分:0)

这种情况下的问题是waitFor:

p.waitFor();

这会导致Button Action Listener等待该点,直到该过程完成。