GUI Update and Threads

Hello all, I'm new to Java and am having a hard time trying to understand how to implement multi-threading. I've built a simple app with GUI for converting files between encodings. The program works fine but some files take a long time to convert so I want to show the user some status text in the GUI. The conversion function is called from within a button click event, and further from within a loop that cycles through a directory of files to convert. I'm trying to show the name of the file being converted in a label in the GUI, but the label won't update until the button click event finishes, so only the last file converted is displayed.

I've read through countless examples on this forum and others, and in several online books. I understand the concept of multi-threading and some of the issues surrounding them, but can't seem to grasp the mechanics of getting them set up in code at this point.

Can anyone provide some coding tips? The code is provided below minus the NetBeans generated layout code. I can provide this also if needed. You'll see some lines commented out in the For loop that I tried but that didn't work.

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import java.io.*;

publicclass EnCon4extends javax.swing.JFrame{

//Declare variables and set default values

String fname="UTF-16";

String tname="UTF8";

String infile;

String outfile;

/** Creates new form EnCon4 */

public EnCon4(){

initComponents();

}

privatevoid encodingToListActionPerformed(java.awt.event.ActionEvent evt){

tname=(String)encodingToList.getSelectedItem();

}

privatevoid encodingFromListActionPerformed(java.awt.event.ActionEvent evt){

fname=(String)encodingFromList.getSelectedItem();

}

privatevoid jButton1ActionPerformed(java.awt.event.ActionEvent evt){

jLabelStatus.setText("Converting From: " + fname +" To: " + tname);

jLabelStatus.repaint();

File dir1 =new File (".");//Set current directory

//Create new subdirectory for converted files

String newDirName = dir1 + File.separator +"Conv";

File dir2 =new File(newDirName);

if (!dir2.exists()){

dir2.mkdir();

}

try{

//Set path to current directory

File pathName =new File(dir1.getCanonicalPath());

File[] contents = pathName.listFiles();

for (File file : contents){

if (!file.isDirectory()){

//Create the converted files

//Set variables

infile = dir1.getCanonicalPath() + File.separator + file.getName();

outfile = dir2.getCanonicalPath() + File.separator + file.getName();

//Check file names to exclude converting the java class file

if (!infile.endsWith("class") & !infile.endsWith("jar")){

//Print the file name

jLabelStatus.setText("Converting file: " + file.getName());

jLabelStatus.repaint();

//}

//catch (Exception econ) {

//System.out.print(econ.getMessage());

//System.exit(1);

//}

//System.out.println(file.getName());

//Call conversion function in a new thread.

//Example with static args //try { convert("NamesASCII.txt", "UTF8.txt", "ISO8859_1", "UTF8"); }

//Thread t = new Thread(new Runnable() {

//public void run() {

try{convert(infile, outfile, fname, tname);

}

catch (Exception econ){

jLabelStatus.setText(econ.getMessage());

jLabelStatus.repaint();

//System.out.print(econ.getMessage());

//System.exit(1);

}

//}

//});

//t.start();

}

}

}

//jLabelStatus.setText("Conversion complete.");

//jLabelStatus.repaint();

}

catch(IOException econ){

jLabelStatus.setText("Error: " + econ);

//System.out.println("Error: " + econ);

}

}

publicstaticvoid convert(String infile, String outfile, String from, String to)

throws IOException, UnsupportedEncodingException{

// set up byte streams

InputStream in;

if (infile !=null) in =new FileInputStream(infile);

else in = System.in;

OutputStream out;

if (outfile !=null) out =new FileOutputStream(outfile);

else out = System.out;

// Set up character stream

Reader r =new BufferedReader(new InputStreamReader(in, from));

Writer w =new BufferedWriter(new OutputStreamWriter(out, to));

// Copy characters from input to output. The InputStreamReader

// converts from the input encoding to Unicode, and the OutputStreamWriter

// converts from Unicode to the output encoding. Characters that cannot be

// represented in the output encoding are output as '?'

char[] buffer =newchar[4096];

int len;

while((len = r.read(buffer)) != -1)

w.write(buffer, 0, len);

r.close();

w.flush();

w.close();

}

publicstaticvoid main(String args[]){

java.awt.EventQueue.invokeLater(new Runnable(){

publicvoid run(){

new EnCon4().setVisible(true);

}

});

}

// Variables declaration - do not modify

private javax.swing.JComboBox encodingFromList;

private javax.swing.JComboBox encodingToList;

private javax.swing.JButton jButton1;

private javax.swing.JLabel jLabel1;

private javax.swing.JLabel jLabel2;

private javax.swing.JLabel jLabel3;

private javax.swing.JLabel jLabelStatus;

private javax.swing.JPanel jPanel1;

// End of variables declaration

}

[9908 byte] By [Chris.Ga] at [2007-11-26 13:45:48]
# 1
Here is a working example from the forum that does what you want: http://forum.java.sun.com/thread.jspa?forumID=57&threadID=621226
camickra at 2007-7-8 1:20:34 > top of Java-index,Desktop,Core GUI APIs...
# 2

Thank you for the pointer to the link. I had already found it though before posting the question. I just stared at it for another 15 minutes and still can't see how to implement it in my app.

For example, the portion below that creates the new thread...

else if (e.getSource() == newThread)

{

stopProcessing = false;

thread = new Thread( this );

thread.start();

}

Would I simply make

thread = new Thread( this );

thread.start();

the first few lines of the jButton1ActionPerformed action event? I guess those lines would create the thread, but then how do I make only the time consuming convert function run in it?

Sorry for the very basic questions. I've only been in Java for a few months and haven't had to deal with the complexities of multi-threading in my VB coding experience.

Chris.Ga at 2007-7-8 1:20:34 > top of Java-index,Desktop,Core GUI APIs...
# 3

There are two key principles here. The first is "remove long-running

processes from the event dispatch thread". If in doubt, you

can check with SwingUtilities.isEventDIspatch(). The second is

"put GUI updates on the event dispatch thread", preferable

with SwingUtilities.invokeLater().

Today is my last day at work, so here's a fish:

import java.awt.event.*;

import java.io.*;

import javax.swing.*;

public class EnCon extends JFrame{

String fname="UTF-16";

String tname="UTF8";

String infile;

String outfile;

/** Creates new form EnCon4 */

public EnCon() {

super("EnCon");

jButton1.addActionListener(new ActionListener() {

public void actionPerformed(final ActionEvent ae) {

final Thread t = new Thread(new MyRunner());

t.start();

}

});

final JPanel container = new JPanel();

container.add(jButton1);

container.add(jLabelStatus);

this.getContentPane().add(container);

this.pack();

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

}

private class MyRunner implements Runnable {

public void run() {

sendMessage("Converting From: " + fname + " To: " + tname);

File dir1 = new File ("."); //Set current directory

//Create new subdirectory for converted files

String newDirName = dir1 + File.separator + "Conv";

File dir2 = new File(newDirName);

if (!dir2.exists()) {

dir2.mkdir();

}

try {

//Set path to current directory

File pathName = new File(dir1.getCanonicalPath());

File[] contents = pathName.listFiles();

for (File file : contents) {

if (!file.isDirectory()) {

infile = dir1.getCanonicalPath() + File.separator + file.getName();

outfile = dir2.getCanonicalPath() + File.separator + file.getName();

if (!infile.endsWith("class") & !infile.endsWith("jar")) {

sendMessage("Converting file: " + file.getName());

try {

convert(infile, outfile, fname, tname);

sendMessage("Done");

}

catch (Exception econ) {

sendMessage(econ.getMessage());

}

}

}

}

}

catch(IOException econ) {

sendMessage("Error: " + econ);

}

}

private void sendMessage(final String message) {

SwingUtilities.invokeLater(new Runnable() {

public void run() {

jLabelStatus.setText(message);

}

});

}

}

public static void convert(String infile, String outfile, String from, String to)

throws IOException, UnsupportedEncodingException {

// set up byte streams

InputStream in;

if (infile != null) in = new FileInputStream(infile);

else in = System.in;

OutputStream out;

if (outfile != null) out = new FileOutputStream(outfile);

else out = System.out;

// Set up character stream

Reader r = new BufferedReader(new InputStreamReader(in, from));

Writer w = new BufferedWriter(new OutputStreamWriter(out, to));

// Copy characters from input to output. The InputStreamReader

// converts from the input encoding to Unicode, and the OutputStreamWriter

// converts from Unicode to the output encoding. Characters that cannot be

// represented in the output encoding are output as '?'

char[] buffer = new char[4096];

int len;

while((len = r.read(buffer)) != -1)

w.write(buffer, 0, len);

r.close();

w.flush();

w.close();

}

public static void main(String args[]) {

new EnCon().setVisible(true);

}

// Variables declaration - do not modify

private JButton jButton1 = new JButton("Convert");

private JLabel jLabelStatus = new JLabel("jLabelStatus");

// End of variables declaration

}

es5f2000a at 2007-7-8 1:20:34 > top of Java-index,Desktop,Core GUI APIs...
# 4

I gave you an example with two buttons.

The first button simply invokes the "run" method directly, which means the code continues to execute in the GUI EDT and therefore the GUI can't respond to events.

The second button creates and then starts a Thread. The thread will in turn execute the run method, but know the code is executed in a separate Thread and the GUI can respond to events.

I've posted a working example, I don't know how to explain it any better.

> Would I simply make..

Try it and see what happens. Thats how you learn.

Secondly, if you are not sure how Threads work, why did you start with a complex program. Start with a simple example like I gave you and then add more complex logic. You could simply my logic by removing the "Stop Processing" code. Maybe that will help you understand the whole process better.

camickra at 2007-7-8 1:20:34 > top of Java-index,Desktop,Core GUI APIs...
# 5

Eric,

Thank you for the fish. : )I compiled your code and it worked perfectly. I think I can take what you've given me and fit it into my NetBeans code.

I think I'm beginning to understand the concept from your example. It looks like you actually subclassed the entire action event and created a new function within it called sendMessage. Is my terminology correct? But what I still don't understand is how the new function sendMessage within the subclass MyRunner is able to reference jLabelStatus which is running in another (main) thread.

Any words of wisdom?

Chris.Ga at 2007-7-8 1:20:34 > top of Java-index,Desktop,Core GUI APIs...
# 6
camickr, thank you again for the post. I'll go back and study the example further and maybe try and modify it as you suggested. I think what I was having the most trouble with was seeing how to work the concept into my existing app. Eric got me over the hump with his 'fish'.
Chris.Ga at 2007-7-8 1:20:35 > top of Java-index,Desktop,Core GUI APIs...
# 7

> But what I still don't understand is how the new function sendMessage within the subclass

> MyRunner is able to reference jLabelStatus which is running in another (main) thread.

Inner classes have access to class variables.

It doesn't matter what Thread code is running in. If you have access to the variable then you can update the variable. Of course this is when you get into problems in multi threaded applications. If two Threads try to access the same variable at the same time you can potentially have deadlock. This is why Swing uses a single Thread to update the GUI and why you need to use invokeLater(..);

camickra at 2007-7-8 1:20:35 > top of Java-index,Desktop,Core GUI APIs...
# 8

Thank you both for your quick responses. This was my first ever post in the forum. What a positive way to start! Thanks again and happy new year. If you have a few more minutes to reply, see questions below. If not that's ok too.

camickr, so are inner class and subclass synonymous terms? Makes sense about the visibility of variables, but is jLabelStatus in my code a variable or an object? I thought an object. Still I suppose it makes sense that an object would be visible to an inner class...or does it?

Also, I had envisioned a solution where only the running of the convert function would run in another thread, not the entire action event code (as in Eric's code), e.g. the directory looping. Is that another viable solution?

Message was edited by: Chris.G

Chris.G

Chris.Ga at 2007-7-8 1:20:35 > top of Java-index,Desktop,Core GUI APIs...
# 9

> so are inner class and subclass synonymous terms?

No. A sub class extends another class.

> but is jLabelStatus in my code a variable or an object?

JLabel a = new JLabel(...); // a is a variable that references an Object

int b = 5; // b is a variable that references a primitive data type.

> I had envisioned a solution where only the running of the convert

> function would run in another thread, not the entire action event code

How you break up the code is up to you. The key is that you want your GUI to be responsive. If you execute a long running task in the event handler then then GUI can't respond until the code is finished running. If the directory looping is the long running code, then it should be moved to a separate thread.

camickra at 2007-7-8 1:20:35 > top of Java-index,Desktop,Core GUI APIs...