Programming with JFC

Programming with JFC

by Scott R. Weiner, Stephen Asbury
     
 

Using the JFC's Swing API you can quickly create familiar or unique GUIs for your applications and applets using numerous components including trees, tables, lists, and HTML text elements. In this book, authors Scott Weiner and Stephen Asbury take a hands-on approach to mastering JFC. Using tons of working examples, complete tested source code, and a library of… See more details below

Overview

Using the JFC's Swing API you can quickly create familiar or unique GUIs for your applications and applets using numerous components including trees, tables, lists, and HTML text elements. In this book, authors Scott Weiner and Stephen Asbury take a hands-on approach to mastering JFC. Using tons of working examples, complete tested source code, and a library of ready-to-use GUI components, they give you a complete breakdown for each Swing component, provide detailed style guidelines for each class, show you how to build your own custom JFC components, demonstrate Swing programming techniques in the context of a large application, and cover advanced topics, such as the Undo framework, animated icons, GUIs for the disabled, and more.

Editorial Reviews

Ben Rothke

The Swing component of the Java programming language is the main focus of Programming with JFC. Swing is a toolkit for simplifying and streamlining the development of the windowing components, namely the visual components (such as menus, toolbars, dialog boxes, and so on) that are used in GUI-based programs. More information about Swing can be found at the Swing Connection home page. If you are short on cash, you could glean the bulk of the information provided by Programming with JFC from the free resources available online at the Swing Connection and save yourself fifty dollars.

With Java, of course, you can write programs once for different platforms. Swing components let you design them in such a way that they will execute without modification on any kind of computer with the identical look and feel of the operating system. As an example, when you create a program with Swing for Windows 95, it looks and feels like an application written specifically for Windows 95. When you run the same program under Solaris, it runs like an application written explicitly for Solaris.

Programming with JFC begins with an overview of Swing programming and Swing specific component. The authors detail the difference between what the Java Abstract Window Toolkit (AWT) offers as opposed to the Swing-based model-view-controller (MVC) architecture. More precisely, Swing sits on top of a number of the AWT APIs.

The majority of the book presents the features of Swing, from its user-interface classes to its controls and implementation. The authors start with simple controls before moving onto advanced controls such as trees and tables.

The book comes with the requisite CD-ROM that includes numerous examples, source code, and a library of ready-to-use GUI components. Similar material is also available via Swing Connection.

Overall, the book is an excellent introduction to JFC. Although it is not a definitive or comprehensive guide to Swing, it does cover most of its important aspects.
Electronic Review of Computer Books

Booknews
The authors cover concepts and tools of the JFC (Java Foundation Classes) 1.1 release, including layout managers, Swing buttons, the new Swing table component, styled text display, custom JFC components, animated icons, and Swing documentation. The CD-ROM contains 100-plus basic and advanced example Java programs, two Swing-based applications with source code that demonstrate multiple techniques in a single program, Javasoft's JDK 1.1.x, JFC 1.1, and Alchemy's GifBuilder. The text is designed for Java programmers with some AWT experience. Annotation c. by Book News, Inc., Portland, Or.

Read More

Product Details

ISBN-13:
9780471247319
Publisher:
Wiley
Publication date:
04/21/1998
Series:
Cookbooks Series, #6
Pages:
576
Product dimensions:
7.49(w) x 9.25(h) x 1.28(d)

Read an Excerpt

Chapter 18
Zip and Jar Viewer Example
For an example of a larger JFC program, we have created an application that views JAR and ZIP files. When the user selects a file to open, the contents of this file are mapped into a JTable. Each entry in the current file can be extracted to a directory of the user's choice. The user interface for this application is pictured in Figure 18.1. Notice that a menu is provided as well as a tool bar with options for the two main operations, opening a file and extracting entries from that file. There are also a progress bar and a status label that can be used to display the current status of an extraction process.
Figure 18.1 JarViewer in action.
We created this example to show three main techniques with Swing.
1. This application organizes work into actions. These actions provide user-interface information and contain the actual code for program functionality.
2. This program uses a progress bar to notify the user of work in progress.
3. We have implemented a custom table model to minimize the work for displaying data.
We also chose this example because it uses a number of other Swing components such as a menu, tool bar, and table.
The JarViewer Application's Design
There are two major objects in the JarViewer application. The JarTableModel maps the entries in a zip/jar file into a table format. The columns for this table display the file name for each entry, their size, modification time, and comment, if any. The JJarViewer manages the current file and interface. Whenever the current zip file changes, the JJarViewer creates a new JarTableModel instance to define the table's data. The table model loads a java.util.zip.ZipFile for the new file and reads its entries. These entries are used to produce the actual data.
There are four custom Action objects in the JJarViewer class. These actions represent the open, close, quit, and extract functions of the program. Each action is implemented as a subclass of AbstractAction. The open and extract actions also provide an icon for the tool bar.
A high-level view of this design is pictured in Figure 18.2
Figure 18.2 JJarViewer design.
One thing to note about the design of this program is that we've created a great deal of cohesion between the actions and the main object, the JJarViewer. These classes were designed to build a program, not for use as reusable components. Because we use actions to organize program functionality, this type of interobject relationship is a common Swing design pattern.
JarTableModel
The JarTableModel object extends AbstractTableModel. When a JarTableModel is created it comes with a File object. The jar model stores this file in an instance variable and creates a ZipFile object from it. Then the model creates a Vector that contains the ZipFile's entries. The model can access each entry quickly when the table requests data from it. Aside from implementing the TableModel methods to return data from the ZipFile, the JarTableModel also provides accessor methods that allow other objects in the program to get to the current file, zip file, and zip file entries.
JarTableModel supports only four columns of information: name, modification time, size, and comments. In most cases the comments column is empty. A date formatter is used to display the date for an entry, while the size is converted to a string and displayed with its units in kilobytes (kb).
import java.io.*;
import java.util.*;
import java.util.zip.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import com.sun.java.swing.tree.*;
import com.sun.java.swing.table.*;
import com.sun.java.swing.event.*;
import java.awt.event.*;
import java.awt.*;
public class JarTableModel extends AbstractTableModel
protected File myFile;
protected ZipFile myZipFile;
protected Vector entries;
public JarTableModel(File f)
{
myFile = f;
entries = new Vector();
try
{
if(myFile != null) myZipFile=new ZipFile(myFile);
}
catch(Exception exp)
{
myZipFile = null;
}
updateEntries();
}
protected void updateEntries()
{
Enumeration enum;
if(myZipFile != null)
{
enum = myZipFile.entries();
entries.removeAllElements();
while(enum.hasMoreElements())
{
entries.addElement(enum.nextElement());
}
}
}
public int getRowCount()
{
return entries.size();
}
public int getColumnCount()
{
return 4;
}
public String getColumnName(int columnIndex)
{
String retVal = "";
switch(columnIndex)
{
case 0:
retVal = "Name";
break;
case 1:
retVal = "Time";
break;
case 2:
retVal = "Size";
break;
case 3:
retVal = "Comment";
break;
default:
retVal = "default";
break;
}
return retVal;
}
public Class getColumnClass(int columnIndex)
{
return String.class;
}
public boolean isCellEditable(int rowIndex,
int columnIndex)
{
boolean retVal = false;
return retVal;
}
public Object getValueAt(int rowIndex,
int columnIndex)
{
Object retVal=null;
ZipEntry entry=null;
if(rowIndex < entries.size())
{
entry = (ZipEntry)entries.elementAt(rowIndex);
}
if(entry == null) return "";
if(columnIndex == 0)
{
retVal = entry.getName();
}
else if(columnIndex == 1)
{
Date d = new Date(entry.getTime());
java.text.DateFormat df =
java.text.DateFormat.getDateInstance();
retVal = df.format(d);
}
else if(columnIndex == 2)
{
long sizeInBytes = entry.getSize();
long sizeInKb = sizeInBytes/1024;
if(sizeInKb < 1) sizeInKb = 1;
retVal = String.valueOf(sizeInKb) + " kb";
}
else if(columnIndex == 3)
{
retVal = entry.getComment();
}
return retVal;
}
public void setValueAt(Object aValue,
int rowIndex,
int columnIndex)
{
return;
}
public ZipFile getZipFile()
{
return myZipFile;
}
public File getFile()
{
return myFile;
}
public Vector getEntries()
{
return entries;
}
Notice that this model does not support editable tables.
Actions
As we mentioned previously, JJarViewer uses four action objects. All of these actions take a handle to the JJarViewer object in their constructor. This provides them with access to the main program data.
The Open Action
The first action class, OpenAction, is responsible for opening a new zip/jar file. This action initializes its name and icon in the constructor. When the user activates the action, via a menu or toolbar, it requests a file from the user and tells the JJarViewer object to use this file as the new current zip/jar file. We use the AWT FileDialog in this example. Ultimately, Swing will have its own FileChooser but it's not scheduled for release with JFC 1.1. For efficiency, we keep track of the dialog in the JJarViewer object so that if another action needs it, the same dialog can be shared.
import java.awt.*;
import java.io.*;
import java.util.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import com.sun.java.swing.tree.*;
import com.sun.java.swing.table.*;
import com.sun.java.swing.event.*;
import java.awt.event.*;
import java.awt.*;
public class OpenAction extends AbstractAction
protected JJarViewer jjar;
public OpenAction(JJarViewer j)
{
jjar = j;
Icon img = new ImageIcon("images/open.gif");
setIcon(Action.SMALL_ICON,img);
setIcon(Action.DEFAULT,img);
setText(Action.DEFAULT,"Open Archive");
}
public void actionPerformed(ActionEvent evt)
{
FileDialog chooser;
chooser = jjar.getFileChooser();
chooser.show();
File fileToOpen = new File(chooser.getDirectory(),
chooser.getFile());
jjar.setFile(fileToOpen);
}
The Close Action
The second action in this program is the CloseAction. This action is responsible for closing the current zip/jar file. Aside from initializing its text, this action tells the JJarViewer to close the current file when it is activated.
import java.awt.*;
import java.io.*;
import java.util.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import com.sun.java.swing.tree.*;
import com.sun.java.swing.table.*;
import com.sun.java.swing.event.*;
import java.awt.event.*;
import java.awt.*;
public class CloseAction extends AbstractAction
protected JJarViewer jjar;
public CloseAction(JJarViewer j)
{
jjar = j;
setText(Action.DEFAULT,"Close Archive");
}
public void actionPerformed(ActionEvent evt)
{
jjar.closeFile();
}
The Quit Action
The third action for this program is called QuitAction and is used as a window or action listener. In this program we associate the quit action with the quit menu item, and we assign it to the JJarViewer as a WindowListener. When the user selects the close box or chooses the quit menu item, this action sets the current file to null, hides the JJarViewer window, and exits the program.
import java.awt.*;
import java.io.*;
import java.util.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import com.sun.java.swing.tree.*;
import com.sun.java.swing.table.*;
import com.sun.java.swing.event.*;
import java.awt.event.*;
import java.awt.*;
public class QuitAction extends AbstractAction
implements WindowListener
protected JJarViewer jjar;
public QuitAction(JJarViewer j)
{
jjar = j;
setText(Action.DEFAULT,"Quit");
}
public void actionPerformed(ActionEvent evt)
{
doQuit();
}
public void doQuit()
{
jjar.setFile(null);
jjar.setVisible(false);
System.exit(0);
}
public void windowOpened(WindowEvent e){}
public void windowClosing(WindowEvent e)
{
doQuit();
}
public void windowClosed(WindowEvent e){}
public void windowIconified(WindowEvent e){}
public void windowDeiconified(WindowEvent e){}
public void windowActivated(WindowEvent e){}
public void windowDeactivated(WindowEvent e){}
The Extract Action
Our fourth and final action, called ExtractAction, extracts entries from the current zip file. This action is also the most complex:
1. It initializes itself with text and an icon.
2. When it receives an ActionEvent, the ExtractAction uses a JOptionPane to request the user for a directory to extract to.
3. It validates the user's entry and displays an error message if a file error occurs..
4. If the directory is valid, the ExtractAction creates a background thread, with itself as the Runnable object, and starts the thread.
We use a background thread to perform the extraction so that we can update the JarViewer's progress bar as we read entries from the zip file. Remember that the action event that notified our ExtractAction came from the event queue thread. If we process the request in the same thread, then as we update the progress bar it makes paint requests that are held in the queue until we return. In other words, the progress bar won't repaint until we return.
If we extract files in a background thread, the actionPerformed method does not block, thereby allowing other events to be processed. While the event-processing thread is looking for events, our background thread updates the progress bar, which, in turn, generates paint events. These events are processed in the event-processing thread, simultaneously with our file activity. This is a perfect example of how threads are used to multiplex user and paint events with file input/output.
The ExtractAction's run method implements the extraction by reading the files from the zip file and writing them to the requested destination. Any necessary directories are created in the process. As each entry is processed, the progress bar is updated to the total number processed. We set the maximum to the total number to extract, so this provides an accurate view of the current extraction progress. We also update the status label with the file name for the current entry.
When the extraction is complete, we reset the progress bar to 0, so that it's empty, and reset the status label to the empty string.
import java.awt.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import com.sun.java.swing.tree.*;
import com.sun.java.swing.table.*;
import com.sun.java.swing.event.*;
import java.awt.event.*;
import java.awt.*;
public class ExtractAction extends AbstractAction
implements Runnable
protected JJarViewer jjar;
Thread kicker;
File dest;
int rows[];
public ExtractAction(JJarViewer j)
{
jjar = j;
Icon img = new ImageIcon("images/extract.gif");
setIcon(Action.SMALL_ICON,img);
setIcon(Action.DEFAULT,img);
setText(Action.DEFAULT,"Extract Files");
}
public void actionPerformed(ActionEvent evt)
{
if((kicker == null)
&& isEnabled())
{
JTable table = jjar.getTable();
String result=null;
result = JOptionPane.showInputDialog(jjar
,"Where do you want to extract to?"
,"Destination"
,JOptionPane.QUESTION_MESSAGE);
if(result != null) dest = new File(result);
if(dest == null)
{
//do nothing;
}
if(!dest.exists())
{
JOptionPane.showMessageDialog(jjar
,"That is not a valid directory."
,"File Error"
,JOptionPane.ERROR_MESSAGE);
}
else
{
rows = table.getSelectedRows();
kicker = new Thread(this);
kicker.start();
}
}
}
public synchronized void run()
{
int i,max;
ZipEntry entry;
File realDest;
FileOutputStream fileOut;
BufferedOutputStream bufOut;
BufferedInputStream bufIn;
InputStream zipIn;
String entryPath;
int cur;
JarTableModel tmodel = jjar.getTableModel();
File myFile = tmodel.getFile();
Vector entries = tmodel.getEntries();
JProgressBar progBar = jjar.getProgressBar();
JLabel status = jjar.getStatus();
if(!dest.isDirectory())
{
dest = new File(dest.getParent());
}
max = rows.length;
progBar.setMinimum(0);
progBar.setMaximum(max);
progBar.setValue(0);
for(i=0;i {
entry = (ZipEntry)entries.elementAt(rows[i]);
entryPath = entry.getName();
entryPath = entryPath.replace('/',File.separatorChar);
entryPath = entryPath.replace('\\',File.separatorChar);
realDest = new File(dest,entryPath);
status.setText("Extracting: "+entryPath);
if(status.getParent() != null)
{
status.getParent().validate();
}
if(entry.isDirectory())
{
realDest.mkdirs();
}
else
{
new File(realDest.getParent()).mkdirs();
try
{
zipIn = tmodel.getZipFile().getInputStream(entry);
bufIn = new BufferedInputStream(zipIn);
fileOut = new FileOutputStream(realDest);
bufOut = new BufferedOutputStream(fileOut);
while((cur = bufIn.read()) != -1)
{
bufOut.write(cur);
}
bufOut.close();
bufIn.close();
}
catch(Exception exp)
{
}
}
progBar.setValue(i);
}
//reset on completion
status.setText("");
progBar.setValue(0);
kicker = null;
}
Notice that the ExtractAction ignores ActionEvents if it's disabled. This ensures that regardless of the user interface that triggers the action, it won't fire unless it's supposed to fire. We use the JJarViewer class to keep this action enabled or disabled based on the current file.
JJarViewer Class
The JJarViewer object is at the heart of this application. JJarViewer is a subclass of JFrame. It provides the main method and creates the user interface. JJarViewer also keeps track of the current file and updates the JarTableModel appropriately. This object acts as an information hub for the other objects in the program.
The import statements for JJarViewer and its class declaration look like this:
import java.awt.*;
import java.io.*;
import java.util.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import com.sun.java.swing.tree.*;
import com.sun.java.swing.table.*;
import com.sun.java.swing.event.*;
import java.awt.event.*;
import java.awt.*;
public class JJarViewer extends JFrame
The instance variables are as follows:
protected FileDialog sharedChooser;
protected JTable table;
protected JarTableModel tmodel;
JProgressBar progBar;
JLabel statusField;
Action extractAction;
These variables are used to keep track of the user interface, the shared FileDialog, the JarTableModel, and the ExtractAction. We keep this action enabled and disabled based on the state of the current file, so we keep a handle to it as an instance variable.
Building the User Interface
JJarViewer builds the user interface in its constructor where it also initializes the current file to null. The interface has a menu bar, a toolbar at the north of a border layout, a table in a scroll pane in the center of a border layout, and a panel with a progress bar and label at the south of a border layout. The panel at the bottom uses a bevel border to separate it visually from the table. This interface is pictured in Figure 18.3. The actions we just discussed are used to define the menu items and toolbar controls. This ensures that they share the same descriptions.
Figure 18.3 JJarViewer interface.
public JJarViewer()
JMenuBar bar = new JMenuBar();
JMenu fileM,actionM;
JMenuItem tmp;
Action action;
JToolBar tools;
JPanel status;
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
table = new JTable();
table.setBackground(Color.white);
getContentPane().add(JTable.createScrollPaneForTable(table),"Center");
tools = new JToolBar();
fileM = new JMenu("File");
fileM.setKeyAccelerator('F');
//Add the open action to the menu
//and toolbar
action = new OpenAction(this);
tmp = fileM.add(action);
tmp.setKeyAccelerator('o');
tools.add(action);
//Add the close action to the menu
action = new CloseAction(this);
tmp = fileM.add(action);
tmp.setKeyAccelerator('c');
fileM.addSeparator();
//Add the quit action to the menu
//and as a window listener
action = new QuitAction(this);
tmp = fileM.add(action);
tmp.setKeyAccelerator('q');
addWindowListener((QuitAction)action);
bar.add(fileM);
tools.addSeparator();
//Build the actions menu
actionM = new JMenu("Actions");
actionM.setKeyAccelerator('A');
extractAction = new ExtractAction(this);
tmp = actionM.add(extractAction);
tmp.setKeyAccelerator('x');
tools.add(extractAction);
bar.add(actionM);
setJMenuBar(bar);
getContentPane().add(tools,"North");
status = new JPanel();
status.setBorder(new BevelBorder(BevelBorder.LOWERED));
status.setLayout(new FlowLayout(FlowLayout.RIGHT));
statusField = new JLabel("Select a File to View.");
status.add(statusField);
progBar = new JProgressBar();
status.add(progBar);
getContentPane().add(status,"South");
//initialize the file to null
setFile(null);
The JJarViewer class has three methods for accessing the status label, progress bar, and file dialog. These methods are included to allow actions to update the user interface, if necessary.
public JProgressBar getProgressBar()
return progBar;
public JLabel getStatus()
return statusField;
public FileDialog getFileChooser()
if(sharedChooser == null)
{
sharedChooser = new java.awt.FileDialog(this);
}
return sharedChooser;
Centralizing Data
The JJarViewer class provides access to the table through the getTable method and table model through the getTableModel method for actions that need the information they contain.
public JarTableModel getTableModel()
return tmodel;
public JTable getTable()
return table;
Managing the Current File
The JJarViewer class provides three methods for interacting with the current zip/jar file. Use the setFile method when you set the file, the getFile method to retrieve the file, and the closeFile method to close the file.
Setting the current file causes the viewer to create a new JarTableModel object. If the file is not valid, this model describes a table with four columns and no data. If the file is valid, the model describes the entries in that file. After the model is created it is assigned to the table. The table's selection is also cleared, to avoid confusion between files, and the table is told to rebuild its display based on the new model. Next, the viewer updates three items based on the current file. If the file is null, the status label is set to reflect this, as is the viewer's title. For a null file, the extract action is disabled. This automatically disables the menu and tool bar items, so that the user cannot try to extract something from an empty file. If the file is not null, the title and status bars are updated and the extract action enabled.
public void setFile(File f)
tmodel = new JarTableModel(f);
table.clearSelection();
table.setModel(tmodel);
table.createDefaultColumnsFromModel();
if(f != null)
{
setTitle(f.getName());
statusField.setText("File Loaded.");
extractAction.setEnabled(true);
}
else
{
setTitle("JJar");
statusField.setText("Select a file to load.");
extractAction.setEnabled(false);
}
If an object requests the current file, the viewer gets it from the JarTableModel and returns it.
public File getFile()
return tmodel.getFile();
To close the current file, the viewer sets the current file to null. Because this is just a viewer, we never save or change information so there is no need to check for that here.
public void closeFile()
setFile(null);
SetVisible and Main
The JJarViewer class overrides the setVisible method to center itself when it is displayed.
public void setVisible(boolean tf)
if(tf)
{
Dimension screenSize =
getToolkit().getScreenSize();
Dimension curSize;
int x,y;
this.pack();
curSize = getSize();
curSize.width = Math.max(400,curSize.width);
curSize.height = Math.max(300,curSize.height);
x = (screenSize.width-curSize.width)/2;
y = (screenSize.height-curSize.height)/2;
setBounds(x,y,curSize.width,curSize.height);
}
super.setVisible(tf);
The main method creates a JJarViewer instance and displays the following:
public static void main(String s[])
JJarViewer frame = new JJarViewer();
frame.setForeground(Color.black);
frame.setBackground(Color.lightGray);
frame.setVisible(true);
Of course, the last line of this example is the ending curly bracket:
Summary
Actions provide a great mechanism for organizing program functionality. They define the code to invoke and the text and icon to describe this code. By using actions to perform the major functions for our application, we have created a program that can easily be extended by adding other actions and their accompanying menu items.
Providing a progress bar is helpful to the user during lengthy operations. Because these operations often involve file input/output, you often need to perform the main action in a background thread. This allows the progress bar to update in the event-processing/painting thread. In Chapter 19, we discuss another large example that uses this technique.
Finally, Swing's model-view-controller design makes it easy to display data by creating custom models. In this example, we implemented less than 180 lines of code, including import statements, to map a zip file to a table. This is an incredible time saver; we hope that it improves future application design and reduces development time.
Weiner/JFC Chapter 18 - 12

Read More

Customer Reviews

Average Review:

Write a Review

and post it to your social network

     

Most Helpful Customer Reviews

See all customer reviews >