2

编辑:代码现在有效!我是这样做的:

package me.nrubin29.jterminal;

import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    private File workingFolder = FileSystemView.getFileSystemView().getDefaultDirectory();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                        Process bash = new ProcessBuilder("bash").directory(workingFolder).start();

                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        int code = bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                        if (code == 0 && command.split(" ").length > 1) workingFolder = new File(command.split(" ")[1]);

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

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

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            if (lines.length == 0) return;
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}

我一直在研究 Java 终端应用程序。它的工作原理是它只打印第一个命令的输出。这是我尝试ls多次运行时的 GUI 图片。

JTerminal 图形界面

package me.nrubin29.jterminal;

import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        final Process bash = new ProcessBuilder("/bin/bash").start();

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

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

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}
4

2 回答 2

1

Once a process has exited, it can not be "read" from or "written" to.

You're code will also block on the "waitFor" method, meaning that your UI won't be updated until the process has completed running. This is really helpful...

Instead, you need to start the process and allow it to continue running, monitoring the state in the background.

Because Swing is a single threaded environment, you need to be careful about how you handle long running/blocking processes and updates to the UI.

To this end, I've used a SwingWorker to read the output of the Process in a background thread and re-sync the updates back to the Event Dispatching Thread. This allows the UI to continue running and remain responsive while the output from the running process is read in...

Also, instead of "running" a new command each time, creating a new process, you will need to write to the Process's input put stream.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestTerminal {

    public static void main(String[] args) {
        new TestTerminal();
    }

    public TestTerminal() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                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 output;
        private JTextField input;

        private Process process;

        public TestPane() {
            setLayout(new BorderLayout());

            output = new JTextArea(20, 20);
            input = new JTextField(10);

            output.setLineWrap(false);
            output.setWrapStyleWord(false);
            output.setEditable(false);
            output.setFocusable(false);

            add(new JScrollPane(output));
            add(input, BorderLayout.SOUTH);

            input.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    String cmd = input.getText() + "\n";
                    input.setText(null);
                    output.append("\n" + cmd + "\n\n");
                    if (process == null) {
                        ProcessBuilder pb = new ProcessBuilder("bash");
                        pb.directory(new File("."));
                        try {
                            process = pb.start();
                            InputStreamWorker isw = new InputStreamWorker(output, process.getInputStream());
                            isw.execute();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                            input.setEnabled(false);
                        }

                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                int exit = -1;
                                try {
                                    exit = process.waitFor();
                                } catch (InterruptedException ex) {
                                }
                                System.out.println("Exited with " + exit);
                                input.setEnabled(false);
                            }
                        }).start();

                    }
                    OutputStream os = process.getOutputStream();
                    try {
                        os.write(cmd.getBytes());
                        os.flush();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                        input.setEnabled(false);
                    }
                }
            });
        }

    }

    public class InputStreamWorker extends SwingWorker<Void, Character> {

        private InputStream is;
        private JTextArea output;

        public InputStreamWorker(JTextArea output, InputStream is) {
            this.is = is;
            this.output = output;
        }

        @Override
        protected void process(List<Character> chunks) {
            StringBuilder sb = new StringBuilder(chunks.size());
            for (Character c : chunks) {
                sb.append(c);
            }
            output.append(sb.toString());
        }

        @Override
        protected Void doInBackground() throws Exception {
            int in = -1;
            while ((in = is.read()) != -1) {
                publish((char)in);
            }
            return null;
        }

    }

}

I would also recommend that you avoid KeyListener where you can. JTextField utilises a ActionListener, which will be called when ever the user presses the "action" key, what ever that might be for the given platform...

于 2013-08-27T22:19:21.680 回答
1

一个 Process 对象只能使用一次,因此对 Process.waitFor() 的后续调用会立即返回(因为该进程已经终止)。相反,您每次都必须从 ProcessBuilder 请求一个新流程。

这是正确的代码:

package me.nrubin29.jterminal;
import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        final ProcessBuilder builder = new ProcessBuilder("/bin/bash");

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                                Process bash = builder.start();
                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

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

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}
于 2013-08-27T22:10:47.110 回答