I have a class (shown below) that extends JPanel and contains a JTextPane. I want to redirect System.out and System.err to my JTextPane. My class does not seem to work. When I run it, it does redirect the system prints, but they do not print to my JTextPane. Please help!
Note: The calls are only redirected when the application launches. But any time after launch, the System.out calls are not redirected to the JTextPane. (ie, if I place a System.out.prinln(); in the class, it will be called, but if it is placed in a actionListener for later use, it does not redirect).
public class OSXConsole extends JPanel {
public static final long serialVersionUID = 21362469L;
private JTextPane textPane;
private PipedOutputStream pipeOut;
private PipedInputStream pipeIn;
public OSXConsole() {
super(new BorderLayout());
textPane = new JTextPane();
this.add(textPane, BorderLayout.CENTER);
redirectSystemStreams();
textPane.setBackground(Color.GRAY);
textPane.setBorder(new EmptyBorder(5, 5, 5, 5));
}
private void updateTextPane(final String text) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Document doc = textPane.getDocument();
try {
doc.insertString(doc.getLength(), text, null);
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
textPane.setCaretPosition(doc.getLength() - 1);
}
});
}
private void redirectSystemStreams() {
OutputStream out = new OutputStream() {
#Override
public void write(final int b) throws IOException {
updateTextPane(String.valueOf((char) b));
}
#Override
public void write(byte[] b, int off, int len) throws IOException {
updateTextPane(new String(b, off, len));
}
#Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
};
System.setOut(new PrintStream(out, true));
System.setErr(new PrintStream(out, true));
}
}
Piped streams always confuse me, which is why my Message Console solution doesn't use them. Anyway here is my attempt at a console using piped streams. A couple of differences:
a) it uses a JTextArea because a JTextArea is more efficient than a JTextPane for just displaying text. Of course if you intend to add attributes to the text then you need a text pane.
b) this solution uses Threads. I'm sure I read somewhere that this was necessary to prevent blocking of the output. Anyway it works in my simple test case.
import java.io.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
public class Console implements Runnable
{
JTextArea displayPane;
BufferedReader reader;
private Console(JTextArea displayPane, PipedOutputStream pos)
{
this.displayPane = displayPane;
try
{
PipedInputStream pis = new PipedInputStream( pos );
reader = new BufferedReader( new InputStreamReader(pis) );
}
catch(IOException e) {}
}
public void run()
{
String line = null;
try
{
while ((line = reader.readLine()) != null)
{
// displayPane.replaceSelection( line + "\n" );
displayPane.append( line + "\n" );
displayPane.setCaretPosition( displayPane.getDocument().getLength() );
}
System.err.println("im here");
}
catch (IOException ioe)
{
JOptionPane.showMessageDialog(null,
"Error redirecting output : "+ioe.getMessage());
}
}
public static void redirectOutput(JTextArea displayPane)
{
Console.redirectOut(displayPane);
Console.redirectErr(displayPane);
}
public static void redirectOut(JTextArea displayPane)
{
PipedOutputStream pos = new PipedOutputStream();
System.setOut( new PrintStream(pos, true) );
Console console = new Console(displayPane, pos);
new Thread(console).start();
}
public static void redirectErr(JTextArea displayPane)
{
PipedOutputStream pos = new PipedOutputStream();
System.setErr( new PrintStream(pos, true) );
Console console = new Console(displayPane, pos);
new Thread(console).start();
}
public static void main(String[] args)
{
JTextArea textArea = new JTextArea();
JScrollPane scrollPane = new JScrollPane( textArea );
JFrame frame = new JFrame("Redirect Output");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().add( scrollPane );
frame.setSize(200, 100);
frame.setVisible(true);
Console.redirectOutput( textArea );
final int i = 0;
Timer timer = new Timer(1000, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println( new java.util.Date().toString() );
System.err.println( System.currentTimeMillis() );
}
});
timer.start();
}
}
Message Console class does this for you.
Edit:
Here is a simple test class:
import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class MessageConsoleTest
{
public static int counter;
public static void main(String[] args)
throws Exception
{
JTextComponent textComponent = new JTextPane();
JScrollPane scrollPane = new JScrollPane( textComponent );
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Message Console");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().add( scrollPane );
frame.setSize(400, 120);
frame.setVisible(true);
MessageConsole console = new MessageConsole(textComponent);
console.redirectOut();
console.redirectErr(Color.RED, null);
Timer timer = new Timer(1000, new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent e)
{
System.out.println( new java.util.Date().toString() );
}
});
timer.start();
Thread.sleep(750);
Timer timer2 = new Timer(1000, new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent e)
{
System.err.println( "Error Message: " + ++counter);
}
});
timer2.start();
}
}
In the following link you can find the MessageConsole class that someone mentioned. I implemented a software and used this solution and it works perfect for me.
I used the Netbeans design tool, so the code regarding the visual appearance of the JTextPane is a bit cumbersome, so I'm not going to place it here.
JTextPane jTextPane = new JTextPane();
MessageConsole console = new MessageConsole(jTextPane);
/*
This parameters are optional, but if you are looking for a solution with JTextPane it is because you need them, at least color.
*/
console.redirectErr(Color.RED, null);
console.redirectOut();
//some event
private void jButton1ActionPerformed(ActionEvent evt) {
/*
In this event I execute a function of my business.
I put it in a thread so that it does not block the graphical interface.
There are many calls to System.out.println() and System.err.println()
*/
BusinessClass bc = new BusinessClass();
Runnable runnable = () -> {
bc.someBusinessFn();
};
thread = new Thread(runnable);
thread.start();
}
//My main method
public static void main(String args[]) {
/* Create and display the GUI */
EventQueue.invokeLater(() -> {
new MyJFrame().setVisible(true);
});
}
Edit
Sorry, I did not realize that in the response similar to this, they had put the link to the MessageConsole class. I didn't see it and I also wanted to show my solution.
Related
I am trying to get my application to display a simple loading dialog so users know when a time intensive process is working and when its done. I just want it to show a simple "loading" using a gif I downloaded. I already tried using only text and it still doesn't work.
I can get the dialog to display (and disappear) when I want it to, the problem is nothing will display on the dialog (or frame) after displaying it. I have tried many different techniques and all give the same result, a blank dialog.
I finally made a separate class to display the dialog (with loading gif) and I got it to display properly (by itself), but when I run it from my main application, it shows a black dialog again. I tested putting the gif into a JOptionPane and it works, the problem with that is I can't close it at will.
Here is my custom code.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import java.util.logging.*;
import org.w3c.dom.*;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import javax.swing.filechooser.FileNameExtensionFilter;
public class Loader implements Runnable {
final JFileChooser jfc = new JFileChooser();
static JFrame frame = new JFrame();
Frame parentUI = new Frame();
JDialog dialog = new JDialog();
JLabel lbl_filename = new JLabel();
JLabel lbl_path = new JLabel();
static Loader load = new Loader(null);
public static void main(String[] args) throws InterruptedException, InvocationTargetException {
load.run();
frame.setVisible(true);
}
public Loader(Frame parent) {
init();
parentUI = parent;
}
#Override
public void run() {
createDialog(parentUI);
}
public final void init() {
JButton btn = new JButton("Open");
frame.setTitle("Loader Test");
frame.setSize(500, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setLayout(new FlowLayout());
btn.addActionListener(new Action1());
frame.add(btn);
frame.add(lbl_filename);
frame.add(lbl_path);
}
class Action1 implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
openFile();
load.Close();
}
}
private void createDialog(final Frame parent) {
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setTitle("Loader");
URL url = this.getClass().getResource("/resource/loader.gif");
Icon icon = new ImageIcon(url);
JLabel label = new JLabel(icon);
dialog.add(label);
dialog.pack();
dialog.setLocationRelativeTo(parent);
}
public void Show(Boolean visible) {
this.run();
dialog.setVisible(visible);
}
public void Close() {
dialog.setVisible(false);
}
private void setJFCFilter(String file, String ext) {
FileNameExtensionFilter filter = new FileNameExtensionFilter(file, ext);
jfc.setFileFilter(filter);
}
private void openFile() {
File default_dir = new File(".");
jfc.setCurrentDirectory(default_dir);
setJFCFilter("Scalable Vector Graphics", "svg");
int returnVal = jfc.showOpenDialog(parentUI);
if (returnVal == JFileChooser.APPROVE_OPTION) {
String path = jfc.getSelectedFile().getAbsolutePath();
String fileName = jfc.getSelectedFile().getName();
lbl_filename.setText(fileName);
lbl_path.setText(path);
load.Show(true);
createDoc(path);
load.Close();
}
}
private void createDoc(String file) {
try {
NodeList svgIDPaths;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(file);
String xpathIDExp = "//g/#id";
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
XPathExpression expression = xpath.compile(xpathIDExp);
svgIDPaths = (NodeList)expression.evaluate(doc, XPathConstants.NODESET);
} catch (Exception ex) {
Logger.getLogger(Loader.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Edit: Use this file for testing -> svg_test.svg
I have tried calling it like this:
loader.show(true);
And also in its own thread like this:
private void load(final Boolean visible) {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
loader.show(visible);
}
});
t.start();
}
Neither method works and gives me the same result, a blank dialog. I have had this issue in the past, but just gave up and removed it (loading dialog). I have tried it with a progress bar and simple text, nothing seems to work.
Also I tried it in a JOptionPane and it worked, but that's not desirable (I want to close/open when I want not via a button click).
private void load() {
ImageIcon icon = new ImageIcon(MainForm.class.getResource("/resource/loader.gif").getFile());
JOptionPane.showMessageDialog(null, "Loading...", "Loader", JOptionPane.INFORMATION_MESSAGE, icon);
}
I am aware you can't run multiple dialogs on the EDT and have to use a separate thread, but I'm using a separate thread and its not working (it works by itself).
(Also note I have one main application (frame) that is running/opening this second dialog).
Any assistance is appreciated.
You look to have a Swing threading issue where you have long-running code on the event thread messing up drawing of images, and my guess is that the long running code is in your createDoc method. Consider calling that from a background thread, such as from a SwingWorker, and calling close on your load object only after the worker has completed its work. For example something like so:
class Action1 implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
openFile();
// load.Close(); // get rid of this
}
}
// .......
private void openFile() {
// ....
load.Show(true); // load dialog on event thread
new SwingWorker<Void, Void>() {
protected Void doInBackground() throws Exception {
createDoc(path); // call this from background thread
return null;
};
protected void done() {
load.Close(); // only call this once createDoc has completed
// probably should call get() in here to catch all exceptions
};
}.execute();
}
I've search this question in different wording for the past day or two now and I just can not solve it :/,
I have Window that pops up on the screen that looks like a Command Prompt there is a two buttons Run and Stop. Anyways I have on the screen once you press start it starts "Scanning files" It says "Scanning 1-1900 something" counts up to 1900 and then says Scan completed, after that I want text to go Under the existing Text
and write multiple lines of text to mess with my friend for example.
{
Scan Completed
"wait time inbetween each line of text"
Hack initialized
"wait time inbetween each line of text"
Hack installing...
"wait time inbetween each line of text"
Hack installed
ECT
}
Hopefully someone can Help me, every one I looked at did not work with my code :/
I'm new to code as well so...
thanks in advance anyways here my code its not to long :P.
public static void main(String[] args) throws IOException {
JFrame frame = new JFrame("Happy Monday v0.05");
Container contentPane = frame.getContentPane();
JTextPane jta = new JTextPane();
JButton button = new JButton("Run");
JButton buttonstop = new JButton("Stop");
contentPane.add(button);
contentPane.add(buttonstop);
button.setBounds(-1,283,465,40);
buttonstop.setBounds(465,283,469,40);
frame.add(jta).setBackground(Color.black);
console(jta);
//Window
frame.setSize(950, 650 / 16 * 9);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
button.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
new SwingWorker<Void, Object>(){
#Override
protected Void doInBackground() throws Exception {
outputTest("Scanning...");
return null;
}}.execute();
}});
}
//Testing OUTPUTS:/
public static void outputTest(String msg){
for(int i=0;i<1969;i++){
System.out.println(i+" "+msg);
try {
Thread.sleep(01);
System.out.println("Scan Complete");
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
public static void console(final JTextPane area) throws IOException {
area.setContentType("text/html");
final PipedInputStream outPipe = new PipedInputStream();
System.setOut(new PrintStream(new PipedOutputStream(outPipe), true));
new SwingWorker<Void, String>() {
#Override
protected Void doInBackground() throws Exception {
Scanner s = new Scanner(outPipe);
while (s.hasNextLine()){
String line = s.nextLine();
publish(line + "\n");
}
return null;
}
#Override
protected void process(List<String> chunks) {
for (String line : chunks){
area.setText("<font size=\"5\" color=\"green\">"+line+"</font>");
}
}
}.execute();
}
}
The setText method does not appends the line.It overrides the text.So previous text won't be visible.
You have to append text, but have to find an mechanism for that as append method do not exist. In your console method, you can add these:
Document doc = area.getDocument();
Thread.sleep(2000);
doc.insertString(doc.getLength(),"hack installing....\n", null);
Thread.sleep(2000);
doc.insertString(doc.getLength(),"Hack installed...\n", null);
Note:
Calling Thread.sleep() is not recommended directly as you are using multi-threaded environment.It's just for your example haven't scanned your code.
public class NotEditableOutputArea extends JTextPane {
public NotEditableOutputArea() {
setEditable(false);
DefaultCaret caret = (DefaultCaret) getCaret();
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
Font defaultFont = new Font("monospaced", Font.PLAIN, 12);
setFont(defaultFont);
}
public void appendColorText(String text, Color c) {
StyledDocument doc = getStyledDocument();
Style style = addStyle("Style", null);
StyleConstants.setForeground(style, c);
try {
doc.insertString(doc.getLength(), text, style);
} catch (BadLocationException e) {
}
}
public void setColorText(String text, Color c) {
setText(null);
appendColorText(text, c);
}
}
I am using a gui with JTextFields to collect some information and then a JButton that takes that infomration and writes it to a file, sets the gui visibility to false, and then uses Runnable to create an instance of another JFrame from a different class to display a slideshow.
I would like to access some of the information for the JTextFields from the new JFrame slideshow. I have tried creating an object of the previous class with accessor methods, but the values keep coming back null (I know that I have done this correctly).
I'm worried that when the accessor methods go to check what the variables equal the JTextFields appear null to the new JFrame.
Below is the sscce that shows this problem.
package accessmain;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class AccessMain extends JFrame implements ActionListener
{
private static final int FRAMEWIDTH = 800;
private static final int FRAMEHEIGHT = 300;
private JPanel mainPanel;
private PrintWriter outputStream = null;
private JTextField subjectNumberText;
private String subjectNumberString;
public static void main(String[] args)
{
AccessMain gui = new AccessMain();
gui.setVisible(true);
}
public AccessMain()
{
super("Self Paced Slideshow");
setSize(FRAMEWIDTH, FRAMEHEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
//Begin Main Content Panel
mainPanel = new JPanel();
mainPanel.setBorder(new EmptyBorder(0,10,0,10));
mainPanel.setLayout(new GridLayout(7, 2));
mainPanel.setBackground(Color.WHITE);
add(mainPanel, BorderLayout.CENTER);
mainPanel.add(new JLabel("Subject Number: "));
subjectNumberText = new JTextField(30);
mainPanel.add(subjectNumberText);
mainPanel.add(new JLabel(""));
JButton launch = new JButton("Begin Slideshow");
launch.addActionListener(this);
mainPanel.add(launch);
//End Main Content Panel
}
#Override
public void actionPerformed(ActionEvent e)
{
String actionCommand = e.getActionCommand();
if(actionCommand.equals("Begin Slideshow"))
{
subjectNumberString = subjectNumberText.getText();
if(!(subjectNumberString.equals("")))
{
System.out.println(getSubjectNumber());
this.setVisible(false);
writeFile();
outputStream.println("Subject Number:\t" + subjectNumberString);
outputStream.close();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
AccessClass testClass = new AccessClass();
testClass.setVisible(true);
}
});
}
else
{
//Add warning dialogue here later
}
}
}
private void writeFile()
{
try
{
outputStream = new PrintWriter(new FileOutputStream(subjectNumberString + ".txt", false));
}
catch(FileNotFoundException e)
{
System.out.println("Cannot find file " + subjectNumberString + ".txt or it could not be opened.");
System.exit(0);
}
}
public String getSubjectNumber()
{
return subjectNumberString;
}
}
And then creating a barebones class to show the loss of data:
package accessmain;
import javax.swing.*;
import java.awt.*;
public class AccessClass extends JFrame
{
AccessMain experiment = new AccessMain();
String subjectNumber = experiment.getSubjectNumber();
public AccessClass()
{
System.out.println(subjectNumber);
}
}
Hardcoding the accessor method with "test" like this:
public String getSubjectNumber()
{
return "test";
}
Running this method as below in the new JFrame:
SelfPaceMain experiment = new SelfPaceMain();
private String subjectNumber = experiment.getSubjectNumber();
System.out.println(subjectNumber);
Does cause the system to print "test". So the accessor methods seem to be working. However, trying to access the values from the JTextFields doesn't seem to work.
I would read the information from the file I create, but without being able to pass the subjectNumber (which is used as the name of the file), I can't tell the new class what file to open.
Is there a good way to pass data from JTextFields to other classes?
pass the argument 'AccessMain' or 'JTextField' to the second class:
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
AccessClass testClass = new AccessClass(AccessMain.this); //fixed this
testClass.setVisible(true);
}
});
Then reading the value of 'subjectNumber'(JTextField value) from the 'AccessMain' or 'JTextField' in the second class:
public class AccessClass extends JFrame
{
final AccessMain experiment;
public AccessClass(AccessMain experiment)
{
this.experiment = experiment;
}
public String getSubjectNumber(){
return experiment.getSubjectNumber();
}
}
Also, you should try Observer pattern.
A simple demo of Observalbe and Observer
Observable and Observer Objects
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 the Console output in the JTextArea until the exe completely finishes. How do I get it to print Console output as it happens?
Short Concise Code Example below:
//Main
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);
}
}
//Button Action Listener
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;
}
}
//Runnable
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(); }
}
}
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);
}
});
}
}
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.
I have several C/C++ command line tools that I'm wrapping with Java.Swing as GUI. The command line tools can take minutes to hours. Progress bar seems like a good idea to keep users sane. I'm also thinking it might be nice to wrap a GUI for the progress bar, instead of just using system out. But how?
I'm thinking the command line tools can write percents to stderr and I can somehow read it in java. Not exactly sure what the mechanics for this would be. I'm also not clear on asynchronous display (learned a bit about invokeLater() ). New to Java, and would appreciate general suggestions as well. Thanks.
--- update ---
Thanks everyone for your suggestions. Here's the resulting code.
private void redirectSystemStreams() {
OutputStream out_stderr = new OutputStream() {
#Override
public void write(final int b) throws IOException {
update(String.valueOf((char) b));
}
#Override
public void write(byte[] b, int off, int len) throws IOException {
update(new String(b, off, len));
}
#Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
};
System.setErr(new PrintStream(out_stderr, true));
}
private void update(final String inputText) {
int value = 20; //parse inputText; make sure your executable calls fflush(stderr) after each fprintf().
jProgressBar.setValue(value);
/* Also one can redirect to a textpane
SwingUtilities.invokeLater(new Runnable() {
public void run() {
//update jTextPane with inputText
}
});
*/
}
That's seems very fragile, better would be to communicate via sockets in a well established protocol or with some sort of RCP ( perhaps Google's protobuf ) or even webservices.
If you still insists you can launch a process in Java with ProcessBuilder that will give you a Process reference of which you can get the InputStream to read the standard output, but again, that seems very fragile to me.
I hope this helps.
For the progress bar part of your problem you can do something like the following. Note that this is just an example to illustrate the point.
Basically, a thread is created to do the work. Presumably this Runner thread will be interacting with your C/C++ code to get its progress. It then calls update on the Progress Bars Dialog class.
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
public class Main {
private int value;
private Progress pbar;
public static void main(String args[]) {
new Main();
}
public Main() {
pbar = new Progress();
Thread t = new Thread(new Runner());
t.start();
}
class Progress extends JDialog {
JProgressBar pb;
JLabel label;
public Progress() {
super((JFrame) null, "Task In Progress");
pb = new JProgressBar(0, 100);
pb.setPreferredSize(new Dimension(175, 20));
pb.setString("Working");
pb.setStringPainted(true);
pb.setValue(0);
label = new JLabel("Progress: ");
JPanel panel = new JPanel();
panel.add(label);
panel.add(pb);
add(panel, BorderLayout.CENTER);
pack();
setVisible(true);
}
public void update(){
pb.setValue(value);
if(value >= 100){
this.setVisible(false);
this.dispose();
}
}
}
class Runner implements Runnable {
public void run() {
for (int i = 0; i <= 100; i++) {
value++;
pbar.update();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
}
}
}
// Create a window
JFrame frame = new JFrame("Progress");
// Creates a progress bar and add it to the window
JProgressBar prog = new JProgressBar();
frame.add(prog);
// Run C/C++ application
try {
Process p = Runtime.getRuntime().exec(new String[]{"filename","arg1","arg2","..."});
// Get InputStream
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
// Update the progress when recieving output from C/C++
new java.util.Timer().schedule(new TimerTask(){
public void run(){
String str = "";
while ((str=br.readLine()!=null) {
prog.setValue(new Integer(str)); // Set Value of Progress Bar
prog.setString(str+"%"); // Set Value to display (in text) on Progress Bar
}
}
},0,100); // Check every 100 milliseconds
// Fit the window to its contents and display it
frame.pack();
frame.setVisible(true);
} catch (Exception e) {
System.out.println("Failed To Launch Program or Failed To Get Input Stream");
}