Friday, July 20, 2007

Using Swing Worker

In any GUI application the most important thing is responsiveness to user action. To keep our application responsive requires careful design and coding. As we know all the swing related event will be processed by only one thread called AWT event dispatcher thread. Its the job of this thread to do painting of components shown on the screen. If you block AWT event thread for quite sometime it will make your application look unresponsive.

In this post, I will share my experience with this and how I overcame it. I will also show you a small code snippet, which you can use it other Swing Application.

To make your application look responsive you should not do heavy weight operation in AWT thread. For example, on click of a button you need to fetch data from a different network, which will take sometime, so never do this in AWT thread. similarly if you add 2 number on click of a button, you can do it in AWT dispatch thread itself.

In the sample application that we are going to design, we will have a text box which will be filled by data (after doing heavy weight operation) . We will have 2 different buttons. One button will do the heavy weight operation in AWT Thread itself and other will use swing worker thread.

The code snippet below shows how it can be done.

import java.awt.GridBagConstraints;

import java.awt.GridBagLayout;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;



import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.JTextField;

import javax.swing.SwingUtilities;



public class SwingWorkerExample implements ActionListener

{

private JFrame frame;

JTextField field;



// create necessary user interface

public void init()

{

frame =
new JFrame("Test for Swing Worker");

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setSize(300,300);

JPanel panel =
new JPanel(new GridBagLayout());



GridBagConstraints gc =
new GridBagConstraints();

field =
new JTextField(20);

gc.gridx = gc.gridy = 0;

panel.add(field,gc);

gc.gridy++;

JButton button =
new JButton("Do Work in background"); // button that does heavy work in background

button.setActionCommand("Work");

button.addActionListener(
this);

panel.add(button,gc);

gc.gridy++;

button =
new JButton("Do Work"); // button that does heavy work in awt thread ;(

button.setActionCommand("Work2");

button.addActionListener(
this);

panel.add(button,gc);

frame.setContentPane(panel);

frame.pack();

frame.setVisible(true);

}



// no click of a mouse button, called from AWT thread

public void actionPerformed(ActionEvent e)

{

if(e.getActionCommand().equals("Work"))

{

DelaySwingWorker worker =
new DelaySwingWorker(field);

worker.startWork();

}

else

{

try

{

Thread.sleep(10000);

field.setText(
"Data fetched : " + 25);

}

catch(Exception ex)

{

}

}

}



// out swing worker thread.

abstract public static class SwingWorker

{

public void startWork()

{

Thread t =
new Thread(new Runnable() { // create a new thread for heavy operation

public void run()

{

SwingWorker.
this.getData(); // let the swing worker get the data

SwingUtilities.invokeLater(new Runnable() {

public void run()

{

SwingWorker.
this.setData(); // update the data. This will be called from AWT thread

// so that you can safely do UI related update with the data

}

}
);

}

});

t.start();

}

abstract public void getData(); // abstract method will be implemented by the actual data fetchers

abstract public void setData();

}



// a sample class which simulates heavy weight operatoin

public static class DelaySwingWorker extends SwingWorker

{

private int data;

private JTextField field;



public DelaySwingWorker(JTextField f)

{

this.field = f; // field on which we will show the updated data.

}



public void getData()

{

try

{

Thread.sleep(10000); // heavy weight operation

data = 20; // data fetched

}

catch(Exception e)

{

}

}



public void setData()

{

field.setText(
"Data fetched is : " + 20);

}

}



public static void main(String[] args)

{

final SwingWorkerExample ex = new SwingWorkerExample();

SwingUtilities.invokeLater(
new Runnable() {

public void run()

{

ex.init();

}

});

}

}



Now run the application, click the first button, now minimize and maximize the application. It should look normal.

now click the second button and minimize and maximize the application, you should see window being grayed out. This is because AWT thread is blocked in a heavy weight operation and hence it cannot dispatch paint event to get the components painted.

No comments: