Wednesday, March 29, 2006

Memoria libre y ocupada en el sistema

  • Runtime.getRuntime().totalMemory()
  • Runtime.getRuntime().freeMemory()

Tuesday, March 28, 2006

XMLEncoder

Escribe XML una instancia de una clase, pero solo los atributos que tengan get() implementado, ya que usa reflexión.

XMLEncoder

Escribe XML una instancia de una clase, pero solo los atributos que tengan get() implementado, ya que usa reflexión.

SwingX Online Components Store

SwingLabs es un repositorio de componentes Swing, todos ellos distribuidos bajo una licencia libre, que esta impulsado desde Sun Microsystems. Recientemente acaban de hacer disponible la versión beta.

Aquí tenéis unas cuantas capturas de pantalla de los componentes. Seguro que encontráis algo que puede seros útil.

Sunday, March 26, 2006

Lanzar un navegador desde java

Bare Bones Browser
Launch for Java

Use Default Browser to Open a Web Page from a Swing Application

Java is often touted as the programing language of the Internet, so you would think Java might include a standard platform-independent mechanism to launch the user's default web browser. Unfortunately, this commonly needed feature is left to the application developer to build, and it's not easy. Two open source projects exist specifically to address this problem -- BrowserLauncher and BrowserLauncher2. The BrowserLauncher2 project is more current and is an industrial strength solution packed with advanced features and even supports old browsers and old OSes.

Bare Bones


The Bare Bones Browser Launch solution, on the other hand, is intended for those with much simpler requirements or those just looking for an educational "Hello, World" type tutorial on the subject. This solution is appropriate when a compact lightweight method will suffice which limits support to the most current browsers and OSes. Bare Bones is free and works on Mac OS X, GNU/Linux, Unix (Solaris), and Windows XP.

Let's jump straight to the code:

BareBonesBrowserLaunch.java

///////////////////////////////////////////////////////// // Bare Bones Browser Launch // // Version 1.5 // // December 10, 2005 // // Supports: Mac OS X, GNU/Linux, Unix, Windows XP // // Example Usage: // // String url = "http://www.centerkey.com/"; // // BareBonesBrowserLaunch.openURL(url); // // Public Domain Software -- Free to Use as You Like // ///////////////////////////////////////////////////////// import java.lang.reflect.Method; import java.util.Arrays; import javax.swing.JOptionPane; public class BareBonesBrowserLaunch { private static final String errMsg = "Error attempting to launch web browser"; public static void openURL(String url) { String osName = System.getProperty("os.name"); try { if (osName.startsWith("Mac OS")) { Class fileMgr = Class.forName("com.apple.eio.FileManager"); Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] {String.class}); openURL.invoke(null, new Object[] {url}); } else if (osName.startsWith("Windows")) Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); else { //assume Unix or Linux String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape" }; String browser = null; for (int count = 0; count < browser ="="" browser =" browsers[count];" browser ="="">
Launch the user's default browser from your Java Swing application with the following line of code:
BareBonesBrowserLaunch.openURL(urlStr);
This is a fire and forget method -- no further communication or confirmation is provided. However, a pop-up error message will be displayed to the user in most cases if a failure is encountered.

MyApp Test Program


You can try out the cross-platform Bare Bones Browser Launch with this small standalone test program:

MyApp.java

import java.awt.event.*; import javax.swing.*; public class MyApp { public static void main(String[] args) { JFrame frame = new JFrame(); JPanel panel = new JPanel(); final JTextField urlField = new JTextField("http://www.centerkey.com "); JButton webButton = new JButton("Web Trip"); webButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BareBonesBrowserLaunch.openURL(urlField.getText().trim()); } } ); frame.setTitle("Bare Bones Browser Launch"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); panel.add(new JLabel("URL:")); panel.add(urlField); panel.add(webButton); frame.getContentPane().add(panel); frame.pack(); frame.setVisible(true); } }

Tutorial


Put both the "BareBonesBrowserLaunch.java" and "MyApp.java" files into a folder, and issue the following command line instructions:
$ javac *.java
$ java MyApp
The first command compiles the two Java files into class files, and the second command runs the test program.

A window like the following will be displayed:
Click the "Web Trip" button to launch the default browser.

Of course, you'll need the Java JDK for this work, and you may also need to specify the full path to the Java commands. On Windows for example, the "javac" command above would become something like:
> "\Program Files\Java\jdk1.5.0_06\bin\javac" *.java
That's it.

JAR Library


Instead of putting the Bare Bones Browser Launch code directly in your project, you can alternatively use it as an external JAR library [file: BareBonesBrowserLaunch.jar, v1.5, 21KB] complete with source code and Javadoc. Tell your IDE, such as Eclipse or NetBeans, to include the JAR file into your project and then add the following "import" statement to the class responsible for opening a web page:
import com.centerkey.utils.BareBonesBrowserLaunch;
The code completion feature in most IDEs will automatically create the above "import" statement for you.

The steps for including an external JAR into your project will vary depending on your IDE. In Eclipse, open your project and navigate through these menus and options (screenshot):
ProjectPropertiesJava Build PathLibrariesAdd External JARs...
Now link to the Javadoc included within the JAR (screenshot):
Expand "BareBonesBrowserLaunch.jar"Select "Javadoc location: (None)"Edit...Select "Javadoc in archive"Browse... to BareBonesBrowserLaunch.jar2nd Browse... to doc
Finish the configuration process and you're ready to use the Bare Bones Browser Launch including the help activated with the F1 key.

Llamar a otros programas

Using the Desktop API in Java SE 6 (Mustang)
By John O'Conner, February 2006

With the default graphical user interface (GUI) look and feel, printing, and performance, the Java platform has come a long way in its effort to minimize the difference between the performance and integration of native applications and Java applications. Version 6 of the Java Platform, Standard Edition (Java SE), code-named Mustang, continues to narrow the gap with new system tray functionality, better print support for JTable, and now the Desktop API (java.awt.Desktop API). This article describes the new Desktop API, which allows Java applications to interact with the default applications associated with specific file types on the host platform. In order to describe the API more effectively, the article also features a simple application called DesktopDemo.

Desktop Overview

This new functionality is provided by the java.awt.Desktop class. The API is adopted from the JDesktop Integration Components (JDIC) project. The goal of that project is to make "Java technology-based applications first-class citizens" of the desktop, enabling seamless integration. Specifically, the new Desktop API allows your Java applications to do the following:

  • Launch the host system's default browser with a specific Uniform Resource Identifier (URI)
  • Launch the host system's default email client
  • Launch applications to open, edit, or print files associatedwith those applications

The Desktop API uses your host operating system's file associations to launch applications associated with specific file types. For example, if OpenDocument text (.odt) file extensions are associated with the OpenOffice Writer application, your Java application could launch OpenOffice Writer to open, edit, or even print files with that association. Depending on your host system, different applications may be associated with each different action.

Running the DesktopDemo Application

DesktopDemo is a simple Java application that uses Mustang's Desktop API. The application has one main window that exposes the entire API, allowing you to do three things:

  1. Launch the default browser with a specific URI.
  2. Launch the default email client with a mail recipient.
  3. Launch an associated application to open, edit, or print a file.

Figure 1 shows the user interface (UI).

Figure 1: DesktopDemo UI
Figure 1: DesktopDemo UI

You can run this application by downloading the application source and JAR files, changing your console's active directory to the application project's dist directory, and executing the following command using a Mustang JDK:

    java -jar DesktopDemo.jar

Determining Desktop API Support

Before launching the browser, email client, or any application, DesktopDemo must determine whether your platform supports the API. First, however, DesktopDemo disables all the graphical text fields and buttons. It enables these graphical components after determining that the platform supports them.

After instantiating the UI, the application's constructor quickly disables the few components of this application as shown in the following code:

    public DesktopDemo() {
// Initiate all GUI components.
initComponents();
// Disable buttons that launch browser and email client.
// Disable buttons that open, edit, and print files.
disableActions();
...
}

/**
* Disable all graphical components until we know
* whether their functionality is supported.
*/
private void disableActions() {
txtBrowserURI.setEnabled(false);
btnLaunchBrowser.setEnabled(false);

txtMailTo.setEnabled(false);
btnLaunchEmail.setEnabled(false);

rbEdit.setEnabled(false);
rbOpen.setEnabled(false);
rbPrint.setEnabled(false);

txtFile.setEnabled(false);
btnLaunchApplication.setEnabled(false);
}

...

public javax.swing.JTextField txtBrowserURI;
public javax.swing.JButton btnLaunchBrowser;
public javax.swing.JTextField txtMailTo;
public javax.swing.JButton btnLaunchEmail;
public javax.swing.JRadioButton rbEdit;
public javax.swing.JRadioButton rbOpen;
public javax.swing.JRadioButton rbPrint;
public javax.swing.JTextField txtFile;
public javax.swing.JButton btnLaunchApplication;

Use the Desktop.isDesktopSupported() method to determine whether the Desktop API is available. On the Solaris Operating System and the Linux platform, this API is dependent on Gnome libraries. If those libraries are unavailable, this method will return false. After determining that the API is supported, that is, the isDesktopSupported() returns true, the application can retrieve a Desktop instance using the static method getDesktop().

    Desktop desktop = null;
// Before more Desktop API is used, first check
// whether the API is supported by this particular
// virtual machine (VM) on this particular host.
if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
...

If your application doesn't check for API support using isDesktopSupported() before calling getDesktop(), it must be prepared to catch an UnsupportedOperationException, which is thrown when your application requests a Desktop instance on a platform that does not support these features. Additionally, if your application runs in an environment without a keyboard, mouse, or monitor (a "headless" environment), the getDesktop() method will throw a java.awt.HeadlessException.

Once retrieved, the Desktop instance allows your application to browse, mail, open, edit, or even print a file or URI, but only if the retrieved Desktop instance supports these activities. Each of these activities is called an action, and each is represented as a Desktop.Action enumeration instance:

  • BROWSE. Represents a browse action performed by the host's default browser
  • MAIL. Represents a mail action performed by the host's default email client
  • OPEN. Represents an open action performed by an application associated with opening a specific file type
  • EDIT. Represents an edit action performed by an application associated with editing a specific file type
  • PRINT. Represents a print action performed by an application associated with printing a specific file type

Before invoking any of these actions, an application should determine whether the Desktop instance supports them. This is different from determining whether a Desktop instance is available. The Desktop.isDesktopSupported() method tells you whether an instance can be created. Once a Desktop object is acquired, you can query the object to find out which specific actions are supported. If the Desktop object does not support specific actions, or if the Desktop API itself is unsupported, DesktopDemo simply keeps the affected graphical components disabled. As Figure 2 indicates, the components cannot be used to invoke Desktop features in the disabled state.

Figure 2: Graphical components are disabled when the desktop is unsupported.
Figure 2: Graphical components are disabled when the desktop is unsupported.

Using a new Desktop instance, the following code checks each Desktop.Action for support and enables the appropriate graphical components:

    public DesktopDemo() {
...
// Before more Desktop API is used, first check
// whether the API is supported by this particular
// VM on this particular host.
if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
// Now enable buttons for actions that are supported.
enableSupportedActions();
}
...
}

/**
* Enable actions that are supported on this host.
* The actions are the following: open browser,
* open email client, and open, edit, and print
* files using their associated application.
*/
private void enableSupportedActions() {
if (desktop.isSupported(Desktop.Action.BROWSE)) {
txtBrowserURI.setEnabled(true);
btnLaunchBrowser.setEnabled(true);
}

if (desktop.isSupported(Desktop.Action.MAIL)) {
txtMailTo.setEnabled(true);
btnLaunchEmail.setEnabled(true);
}

if (desktop.isSupported(Desktop.Action.OPEN)) {
rbOpen.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.EDIT)) {
rbEdit.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.PRINT)) {
rbPrint.setEnabled(true);
}

if (rbEdit.isEnabled() || rbOpen.isEnabled() || rbPrint.isEnabled()) {
txtFile.setEnabled(true);
btnLaunchApplication.setEnabled(true);
}
}

Once the application determines the supported actions, it enables the appropriate graphical components. If all components are enabled, the UI should look like Figure 3.

Figure 3: Components are enabled when the Desktop API is supported.
Figure 3: Components are enabled when the Desktop API is supported.

Opening the Browser

Calling the following instance method will open your host's default browser:

    public void browse(URI uri) throws IOException

Because DesktopDemo's UI components are enabled only if the associated Desktop.Action is supported, this simple demo application doesn't need to check support again before actually calling the browse() method. However, checking action support before each invocation may be more robust in real-world applications:

    if (desktop.isSupported(Desktop.Action.BROWSE)) {
// launch browser
...
}

DesktopDemo adds a java.awt.event.ActionListener to each button. When enabled, the Launch Browser button invokes the following method through its ActionListener:

    private void onLaunchBrowser(java.awt.event.ActionEvent evt) {
URI uri = null;
try {
uri = new URI(txtBrowserURI.getText());
desktop.browse(uri);
}
catch(IOException ioe) {
ioe.printStackTrace();
}
catch(URISyntaxException use) {
use.printStackTrace();

}
...
}

The browse() method can throw a variety of exceptions, including a NullPointerException if the URI is null, an UnsupportedOperationException if the BROWSE action is unsupported, an IOException if the default browser or application can't be found or launched, and a SecurityException if a security manager denies the invocation.

If all goes well, however, the listener retrieves the text from the associated text field in Figure 4, creates a URI, and invokes the browse() method. The code above launches the default browser on your system and instructs the browser to load the URI, as Figure 5 shows.

Figure 4: Launch the default browser with a specific URI.
Figure 4: Launch the default browser with a specific URI.

Figure 5: The default browser launches using the Desktop API.
Figure 5: The default browser launches using the Desktop API.

Sending Email

Applications can launch the host's default email client, if that action is supported, by calling this Desktop instance method:

    public void mail(URI uri) throws IOException

DesktopDemo has an ActionListener for the Launch Mail button. In this case, the listener invokes the following method:

    private void onLaunchMail(java.awt.event.ActionEvent evt) {
String mailTo = txtMailTo.getText();
URI uriMailTo = null;
try {
if (mailTo.length() > 0) {
uriMailTo = new URI("mailto", mailTo, null);
desktop.mail(uriMailTo);
} else {
desktop.mail();
}
}
catch(IOException ioe) {
ioe.printStackTrace();
}
catch(URISyntaxException use) {
use.printStackTrace();
}
...
}

The onLaunchMail() method retrieves the email recipient from the associated text field, creates the URI with a mailto scheme argument if a recipient exists, and then invokes the mail() method. The mail() method is overloaded, so you can call it with or without a URI that represents its mailto recipient (see Figure 6).

Figure 6: Launch the default email client with an email recipient.
Figure 6: Launch the default email client with an email recipient.

You can use more than just a single email recipient when creating this URI. The mailto scheme supports CC, BCC, SUBJECT, and BODY fields as well. For example, the following text could be used to create a mailto URI:

    mailto:duke@sun.com?SUBJECT=Happy New Year!&BODY=Happy New Year, Duke!

Figure 7 shows the result.

mailto parameters." border="0" height="415" width="538">
Figure 7: The Desktop API launches the default email client with multiple mailto parameters.

You can, of course, invoke mail() without an argument. In this case, your email client will launch a new email window without specifying a recipient, subject, or body message.

Opening, Editing, and Printing a File

Java applications can open, edit, and print files from their associated application using a Desktop object's open(), edit(), and print() methods, respectively (see Figure 8). Again, DesktopDemo allows these actions only if the Desktop instance supports them, so in this application scenario, it is not necessary to check for support again.

Figure 8: Launch the associated application for a specific file type.
Figure 8: Launch the associated application for a specific file type.

Each of DesktopDemo's radio buttons has its own ActionListener as well. In this case, each sets an instance variable so that it represents the most recently selected button's associated Desktop.Action:

    Desktop.Action action;

private void onPrintAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.PRINT;
}

private void onEditAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.EDIT;
}

private void onOpenAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.OPEN;
}

When you press the Launch Default Application button, it invokes its own listener, which calls the following method:

    private void onLaunchDefaultApplication(java.awt.event.ActionEvent evt) {
String fileName = txtFile.getText();
File file = new File(fileName);

try {
switch(action) {
case OPEN:
desktop.open(file);
break;
case EDIT:
desktop.edit(file);
break;
case PRINT:
desktop.print(file);
break;
}
}
catch (IOException ioe) {
ioe.printStackTrace();
}
...
}

This method determines which Desktop.Action is selected and invokes the appropriate Desktop instance method, either open(), edit(), or print(). Each method requires a File argument, which will be used to perform the requested action.

Interestingly, different applications may be registered for these different actions even on the same file type. For example, the Firefox browser may be launched for the OPEN action, Emacs for the EDIT action, and yet a different application for the PRINT action. Your host desktop's associations are used to determine what application should be invoked. The ability to manipulate desktop file associations is not possible with the existing Desktop API in Mustang, and those associations can be created or changed only with platform-dependent tools at this time.

Summary

Desktop integration is an important Mustang theme. One way that Mustang supports this theme is through the java.awt.Desktop API. This API allows Java applications to launch the host's default browser and email client. Additionally, Java applications can launch applications associated with specific file types to open, edit, and print files. Although Java applications cannot manipulate, create, or change file associations, the Desktop API does allow Java applications to launch the default associated applications. This article provides a sample application that demonstrates the API, which you can download from this site.

Note: Any API additions or other enhancements to the Java SE platform specification are subject to review and approval by the JSR 270 Expert Group.

For More Information

The following information may help you learn more about Mustang's integration features as well as related topics:

Thursday, March 23, 2006

Stripes, un Framework web ligero y facil de usar

A veces algunos desarrolladores renuncian literalmente a usar algunos frameworks como Spring, Structs o WebWork 2 simplemente por su "complejidad" para ser configurados y usados, o bien prefieren no usar ningún framework debido a la simplicidad que requiere su aplicación.

Para estos casos me he encontrado recientemente con Stripes. Se trata de un framework que no requiere cientos de líneas de ficheros XML de configuración, puesto que se configura mediante sencillas anotaciones J2SE 5.0 (que configuraran los llamados ActionBeans) y usa JSTL para comunicar las vistas con los controladores.

El propio autor reconoce que Spring o WebWork 2 son frameworks más robustos y complejos, pero eso no quita para que Stripes sea bastante útil en algunas ocasiones, en la web podréis ver un Quick Start Guide que os explica como hacer una sencilla calculadora en unos minutos.

Wednesday, March 22, 2006

Cluster y replicación de Base de datos

Basado en arquitectura JAVA, EuroCluster es una
revolucionaria solución para la replicación y
duplicación de base de datos cuyo funcionamiento es
totalmente independiente tanto de la red/es local/es
como del/os sistema/s operativo/s en el/los que se
quiera implantar.

Características:

Multiplataforma, se puede instalar en cualquier Sistema
Operativo y versión existente en el mercado,
funcionando con Windows 2000, Windows XP, LINUX, etc.
pudiendo, incluso, conectarse varios ordenadores con
Eurocluster que estén instalados en S.O. diferentes
Funciona con cualquier Base De Datos existente en el
mercado que cumpla con las normas ANSI SQL, entre ellas
Access, SQL Server, MySql y Oracle

Número de usuarios ilimitados, se pueden conectar
tantos como soporte el servidor al que esta conectado.

Número de servidores interconectados en cluster o en
servidores de replicación ilimitado.

Las conexiones entre servidores pueden ser locales o a
través de internet.

Librería de etiquetas JSP para acceder a Google maps

Google:map es una librería de etiquetas JSP que permite acceder a Google maps sin necesidad de emplear el API de javascript. La librería va más allá de la funcionalidad básica de este API proporcionando una gestión de eventos al estilo java tanto síncrona como asíncrona y la posibilidad de emplear la rueda del ratón para los zooms, entre otros.

La librería se distribuye bajo una licencia libre y la versión disponible actualmente todavía no es estable; es una beta y el autor advierte que todavía puede haber cambios en ella que rompan la compatibilidad con la versión actual.

¿Cuántos de vosotros habéis construido una aplicación que haga uso de Google maps? ¿Que API empleasteis?

Sunday, March 19, 2006

Frames que se crean en el centro de la pantalla

setLocationRelativeTo(null);

Saturday, March 18, 2006

Swing Tab order

I've been having a problem using custom focus traversal policies. Basically, I have a frame with some components, one of which is a panel that contains other components. I want to use the frames focus traversal policy to tab through it's components, until it reaches the panel. At that point, I would like the panel to be able to control its own focus cycle. Once the last component in the panel is reached, I would like the next component that receives focus to be the component designated to follow the panel by the frames focus traversal policy. I've searched this forum and found lots of similiar problems spread throughout many threads. Here's one solution I came up with...

import javax.swing.*;
import java.awt.*;

public class focusExample extends JFrame {
protected JTextField tf1, tf2, tf3, tf4, tf5, tf6;
protected MyPanel panel;
protected MyFocusTraversalPolicy focusTraversalPolicy;

public focusExample() {
tf1 = new JTextField(10);
tf2 = new JTextField(10);
tf3 = new JTextField(10);
tf4 = new JTextField(10);
tf5 = new JTextField(10);
tf6 = new JTextField(10);
panel = new MyPanel();

getContentPane().setLayout(new FlowLayout());

this.getContentPane().add(tf1);
this.getContentPane().add(tf2);
this.getContentPane().add(tf3);
this.getContentPane().add(panel);
this.getContentPane().add(tf4);
this.getContentPane().add(tf5);
this.getContentPane().add(tf6);

focusTraversalPolicy = new MyFocusTraversalPolicy();
setFocusTraversalPolicy(focusTraversalPolicy);

pack();
setVisible(true);
}

public class MyFocusTraversalPolicy extends FocusTraversalPolicy {
public Component getComponentAfter(Container focusCycleRoot, Component aComponent) {
if(aComponent.equals(tf1)) return tf2;
else if(aComponent.equals(tf2)) return tf3;
else if(aComponent.equals(tf3)) return panel.getFocusTraversalPolicy().getDefaultComponent(panel);
else if(panel.isAncestorOf(aComponent)){
if(aComponent.equals(panel.getFocusTraversalPolicy().getLastComponent(panel))) return tf4;
else return panel.getFocusTraversalPolicy().getComponentAfter(panel, aComponent);
}
else if(aComponent.equals(tf4)) return tf5;
else if(aComponent.equals(tf5)) return tf6;
else return tf1;
}

public Component getComponentBefore(Container focusCycleRoot, Component aComponent) {
if(aComponent.equals(tf6)) return tf5;
else if(aComponent.equals(tf5)) return tf4;
else if(aComponent.equals(tf4)) return panel.getFocusTraversalPolicy().getLastComponent(panel);
else if(panel.isAncestorOf(aComponent)){
if(aComponent.equals(panel.getFocusTraversalPolicy().getFirstComponent(panel))) return tf3;
else return panel.getFocusTraversalPolicy().getComponentBefore(panel, aComponent);
}
else if(aComponent.equals(tf3)) return tf2;
else if(aComponent.equals(tf2)) return tf1;
else return tf6;
}

public Component getDefaultComponent(Container focusCycleRoot) {
return tf1;
}

public Component getFirstComponent(Container focusCycleRoot) {
return tf1;
}

public Component getLastComponent(Container focusCycleRoot) {
return tf6;
}
}

public class MyPanel extends JPanel {
protected JTextField tf1, tf2, tf3;
protected FocusTraversalPolicy focusTraversalPolicy;

public MyPanel() {
tf1 = new JTextField(10);
tf2 = new JTextField(10);
tf3 = new JTextField(10);

this.add(tf3);
this.add(tf2);
this.add(tf1);

focusTraversalPolicy = new FocusTraversalPolicy() {
public Component getComponentAfter(Container focusCycleRoot, Component aComponent) {
if(aComponent.equals(tf1)) return tf2;
else if(aComponent.equals(tf2)) return tf3;
else return tf1;
}

public Component getComponentBefore(Container focusCycleRoot, Component aComponent) {
if(aComponent.equals(tf3)) return tf2;
else if(aComponent.equals(tf2)) return tf1;
else return tf3;
}

public Component getDefaultComponent(Container focusCycleRoot) {
return tf1;
}

public Component getFirstComponent(Container focusCycleRoot) {
return tf1;
}

public Component getLastComponent(Container focusCycleRoot) {
return tf3;
}
};
}

public FocusTraversalPolicy getFocusTraversalPolicy() {
return focusTraversalPolicy;
}
}

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


This also works if MyPanel is defined in another file. By default, Container's getFocusTraversalPolicy() will return null if a call to isFocusCycleRoot() on the container returns false. In MyPanel, getFocusTraversalPolicy() is overridden to return the focus traversal policy even if the panel is not a focus cycle root. This allows the frame the panel is on access to the panel's focus traversal policy. You should over-ride getFocusTraversalPolicy() in any subclasses of container that you add to your frame, or you'll get a null-pointer exception when calling panel.getFocusTraversalPolicy(). I'm unsure about any side effects from over-ridding getFocusTraversalPolicy(); if anyone knows of any problems this may cause, please lte me know.

Rendering cells in Swing's JTable component

Excerpt from Chapter 6 of Professional Java Programming


Level: Intermediate

Brett Spell (JavaBrewer@aol.com)Wrox Press Ltd

01 Nov 2000

The book Professional Java Programming covers a variety of advanced Java topics, such as the Java 2 security model, internationalization, performance tuning and memory management, printing, help, drag-and-drop operations, and cut-and-paste operations. It also contains chapters on JTree and JTable, two of the more complex components provided with Swing. This excerpt from Chapter 6 explains how JTable handles the drawing (or "rendering") of its cells and supports the editing of their values. In addition to describing the behavior of the renderers and editors provided with Swing, the text also discusses how to go about creating your own implementations. The complete chapter in the book includes information on how to enhance JTable's functionality to add features that are often needed, such as the ability to sort rows, to create multi-line column headers, and so on.

Cell rendering

In the screen shot below, the data in several of the columns isn't displayed in an ideal fashion:


Application output

Specifically, three things can be improved:

  • The Date of Birth column displays both a date and time, but should only display a date, and that date should be in a format that does not include the day of the week.
  • Account Balance displays a simple numeric value, but should use currency-formatting conventions.
  • The Gender column displays a somewhat non-intuitive value of "true" or "false" instead of "Male" or "Female."

JTable cells are drawn by cell renderers, which are classes that implement the TableCellRenderer interface. That interface defines a single getTableCellRendererComponent() method that returns a reference to the Component that will perform the drawing operation. However, since it's often convenient to define a single class that implements TableCellRenderer and that can perform the rendering, a TableCellRenderer will often simply return a reference to itself. The parameters passed to getTableCellRendererComponent() are:

  • A reference to the JTable that contains the cell being drawn
  • A reference to the cell's value
  • A boolean flag that indicates whether or not the cell is selected
  • A boolean flag that indicates whether or not the cell has the input focus
  • The row index of the cell being drawn
  • The column index of the cell being drawn

In addition to returning a reference to the rendering component, getTableCellRendererComponent() is responsible for initializing the component's state. Notice that one of the parameters listed above is a reference to the value stored in the cell that's about to be rendered, and some representation of that value is usually stored in the rendering component before a reference to it is returned.

As we'll see shortly, JTable provides predefined renderers that you can use to have your data displayed properly, but first we'll look at how easily custom renderer classes can be defined.



Back to top


Creating custom renderers

The following class provides an example of a custom renderer, and it will be used to display the values in the Gender field in our sample application's table. Those values currently appear as a text string of "true" or "false" depending upon the cell's value, but this renderer will cause them to be drawn by a JComboBox:




import java.awt.Component;
import javax.swing.JComboBox;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;

public class GenderRenderer extends JComboBox
implements TableCellRenderer {

public GenderRenderer() {
super();
addItem("Male");
addItem("Female");
}

public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int
row,
int column) {

if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}

boolean isMale = ((Boolean) value).booleanValue();
setSelectedIndex(isMale ? 0 : 1);
return this;
}

}

When an instance of this class is created, it adds two items to its list: a "Male" selection, and a "Female" selection. The getTableCellRendererComponent() method performs some simple color selection for the foreground and background, and then selects the appropriate gender based on the cell's value ("Male" for true, "Female" for false). Once this renderer class has been created, you can specify that it should be used for the Gender column by making the following changes to SimpleTableTest:




import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;

public class SimpleTableTest extends JFrame {

protected JTable table;

public static void main(String[] args) {
SimpleTableTest stt = new SimpleTableTest();
stt.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
stt.setSize(400, 300);
stt.setVisible(true);
}

public SimpleTableTest() {
Container pane = getContentPane();
pane.setLayout(new BorderLayout());
TableValues tv = new TableValues();
table = new JTable(tv);
TableColumnModel tcm = table.getColumnModel();
TableColumn tc = tcm.getColumn(TableValues.GENDER);
tc.setCellRenderer(new GenderRenderer());
JScrollPane jsp = new JScrollPane(table);
pane.add(jsp, BorderLayout.CENTER);
}

}

The data for the table comes from the TableValues class (though in a real application it would probably come from a database):




import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.swing.table.AbstractTableModel;

public class TableValues extends AbstractTableModel {

public final static int FIRST_NAME = 0;
public final static int LAST_NAME = 1;
public final static int DATE_OF_BIRTH = 2;
public final static int ACCOUNT_BALANCE = 3;
public final static int GENDER = 4;

public final static boolean GENDER_MALE = true;
public final static boolean GENDER_FEMALE = false;

public final static String[] columnNames = {
"First Name", "Last Name", "Date of Birth", "Account Balance", "Gender"
};

public Object[][] values = {
{
"Clay", "Ashworth",
new GregorianCalendar(1962, Calendar.FEBRUARY, 20).getTime(),
new Float(12345.67), new Boolean(GENDER_MALE)
}, {
"Jacob", "Ashworth",
new GregorianCalendar(1987, Calendar.JANUARY, 6).getTime(),
new Float(23456.78), new Boolean(GENDER_MALE)
}, {
"Jordan", "Ashworth",
new GregorianCalendar(1989, Calendar.AUGUST, 31).getTime(),
new Float(34567.89), new Boolean(GENDER_FEMALE)
}, {
"Evelyn", "Kirk",
new GregorianCalendar(1945, Calendar.JANUARY, 16).getTime(),
new Float(-456.70), new Boolean(GENDER_FEMALE)
}, {
"Belle", "Spyres",
new GregorianCalendar(1907, Calendar.AUGUST, 2).getTime(),
new Float(567.00), new Boolean(GENDER_FEMALE)
}
};

public int getRowCount() {
return values.length;
}

public int getColumnCount() {
return values[0].length;
}

public Object getValueAt(int row, int column) {
return values[row][column];
}

public String getColumnName(int column) {
return columnNames[column];
}

}

When you compile and execute the modified version of the application, it produces a display like the one shown below. Notice that the "true" and "false" strings that previously appeared in the Gender column now seem to have been replaced by instances of JCheckBox:


Application output with gender as Male or Female

It's important to realize that renderers are not really added to JTable instances the way that visual components are added to a Container, which in this case means that the table doesn't contain any instances of JCheckBox. Instead, when the table is painted, each cell delegates responsibility for drawing its contents, which is done by passing a Graphics object to a renderer component's paint() method, and the drawing region is set to correspond to the area occupied by the cell. In other words, no instances of JCheckBox were added to the JTable in this example, but rather a single instance of JCheckBox drew itself onto the area occupied by each cell in the Gender column. This approach may seem unnecessarily complex, but it allows a single component to draw most or all of a table's cells instead of requiring the table to allocate a component for each cell, which would consume far more memory.

In many cases, the easiest way to define a custom cell renderer is to extend Swing's DefaultTableCellRenderer, which as its name implies is the default renderer for cells in a JTable. DefaultTableCellRenderer extends JLabel and it displays cell values using their String representations. An object's String representation is obtained by calling its toString() method, and DefaultTableCellRenderer passes that representation to the setText() method it inherits from JLabel. This behavior is implemented in the setValue() method, which is passed a reference to the value of the cell that's about to be rendered:




protected void setValue(Object value) {
setText((value == null) ? "" : value.toString());
}

In effect, DefaultTableCellRenderer is simply a JLabel that sets its own text based on the value of the cell being rendered.

In many cases, calling toString() isn't an appropriate way to obtain a representation of the cell's value, and an example of this is the Account Balance column in our sample application. The values displayed in that column are technically correct, but they're not formatted in a manner that makes it obvious that they represent currency values. However, this can easily be addressed by creating a custom TableCellRenderer and assigning it responsibility for drawing the cells in that column.




import java.text.NumberFormat;
import javax.swing.table.DefaultTableCellRenderer;

public class CurrencyRenderer extends DefaultTableCellRenderer {

public CurrencyRenderer() {
super();
setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
}

public void setValue(Object value) {
if ((value != null) && (value instanceof Number)) {
Number numberValue = (Number) value;
NumberFormat formatter = NumberFormat.getCurrencyInstance();
value = formatter.format(numberValue.doubleValue());
}
super.setValue(value);
}

}

This simple class does just two things: it changes the label's horizontal alignment during construction, and it overrides the setValue() method defined in DefaultTableCellRenderer. Since we know that this renderer class will only be used to render the cells containing numeric values, we can cast the cell's value to a Number and then format the value as a currency using Java's NumberFormat class.

Now that we've created a custom renderer for the Account Balance column, we need to have the table use the renderer when drawing the cells in that column, which could be done by explicitly assigning it to the TableColumn as we did in the previous example. However, there is another way to accomplish this that's worth mentioning and which is more appropriate in many cases. Besides associating a renderer with a particular column, you can also associate it with a particular type of data, and the renderer will then be used to draw all cells in columns that contain that type of data.

When a JTable is initialized, it creates a map that defines associations between classes and renderers, and it uses that map to select a cell renderer when drawing cells in columns for which no renderer was explicitly set. In other words, if you have not explicitly assigned a renderer to a column as we did earlier, JTable will select a renderer based upon the type of data stored in that column. It determines the column's data type by calling the getColumnClass() method in the TableModel, and that method returns an instance of Class. However, the implementation of getColumnClass() in AbstractTableModel simply indicates that all its columns contain instances of Object as shown below:




public Class getColumnClass(int columnIndex) {
return Object.class;
}

Since AbstractTableModel can't know what kind of data its subclasses will contain, the only assumption that it can safely make is that each cell contains an instance of Object, although in practice, the cells will almost certainly contain instances of some subclass of Object such as Float, Date, etc. Therefore, if you want the table to be able to determine the specific type of data its columns contain, you must override getColumnClass() in your TableModel class. For example, since all of the values in the Account Balance column are instances of Float, we could add the following getColumnClass() implementation to the TableValues class:




public Class getColumnClass(int column) {
Class dataType = super.getColumnClass(column);
if (column == ACCOUNT_BALANCE) {
dataType = Float.class;
}
return dataType;
}

Now that our JTable is able to determine that the Account Balance column contains Float data, we need to associate our CurrencyRenderer class with that data type, which can easily be done by calling setDefaultRenderer() as shown below:




public SimpleTableTest() {
Container pane = getContentPane();
pane.setLayout(new BorderLayout());
TableValues tv = new TableValues();
table = new JTable(tv);
TableColumnModel tcm = table.getColumnModel();
TableColumn tc = tcm.getColumn(TableValues.GENDER);
tc.setCellRenderer(new GenderRenderer());
table.setDefaultRenderer(Float.class, new CurrencyRenderer());
JScrollPane jsp = new JScrollPane(table);
pane.add(jsp, BorderLayout.CENTER);
}

This new addition to SimpleTableTest causes CurrencyRenderer to become the default renderer for all columns containing Float data. Therefore, CurrencyRenderer will be used to draw the cells in the Account Balance column because no renderer was assigned to the column and because getColumnClass() now indicates that the column contains Float data. An example of how the interface will appear when the program is executed with these modifications is shown below:


Application output with currency displayed properly

At this point, you may be wondering what happens when no renderer has been explicitly assigned to a column and there is no entry in the table's class-to-renderer map that matches the column's data type. You're correct if you guessed that the rendering is handled by DefaultTableCellRenderer, but it's important to understand exactly how that occurs.

When no renderer has been explicitly assigned to a column and no entry for the column's Class is found in the table's class-to-renderer map, JTable traverses the inheritance hierarchy of the column's Class, searching the class-to-renderer map for an entry corresponding to each superclass until it locates one. For example, if getColumnClass() indicates that the column contains Float data but no entry for Float is found in the class-to-renderer map, JTable next attempts to locate a map entry that corresponds to Float's immediate superclass, which is Number. If it does not find an entry for Number, it will attempt to retrieve an entry for Object (Number's immediate superclass), which will always succeed because the map automatically contains an entry that associates Object columns with DefaultTableCellRenderer.

To summarize JTable's behavior, the steps for locating a renderer are listed below:

  • If a renderer has been set for the cell's TableColumn, use that renderer.
  • Obtain a reference to a Class instance by calling the TableModel's getColumnClass() method.
  • If a renderer has been mapped to that Class, use that renderer.
  • Obtain a reference to the Class instance of the type's superclass and repeat the previous step until a match is found.

This approach provides a great deal of flexibility in assigning renderers to table cells, since it allows you to create a renderer and have it handle rendering for columns with a specific data type, along with any subclasses of that type.



Back to top


JTable's default renderers

We've now seen how to create custom renderers and how to associate a renderer with a given type of data. However, it's often not necessary to do either one, since JTable includes a number of predefined renderers for commonly used data types, and entries for those renderers are automatically included in its class-to-renderer map.

For example, it was already mentioned that an entry exists in the map that associates Object columns with DefaultTableCellRenderer, but other, more sophisticated renderers are provided as well. This means that if one of the predefined renderers is appropriate for your application, the only coding you need to do is to identify your columns' data types in an implementation of getColumnClass() so that JTable will use the appropriate renderers. To illustrate this point, we'll use JTable's predefined renderer for instances of java.util.Date by simply modifying TableValues so that it indicates that the Date of Birth column contains instances of Date:




public Class getColumnClass(int column) {
Class dataType = super.getColumnClass(column);
if (column == ACCOUNT_BALANCE) {
dataType = Float.class;
}
else if (column == DATE_OF_BIRTH) {
dataType = java.util.Date.class;
}
return dataType;
}

As we saw earlier, the date values displayed by DefaultTableCellRenderer were lengthy and included a time (since Java's Date class represents both a date and a time). However, JTable's predefined date renderer produces a shorter, more appropriate representation of each date value as shown below:


Application output with date represented properly

In addition to java.util.Date, JTable includes predefined renderers for a number of other classes, including the following:

java.lang.Number
This is the superclass of the numeric wrappers such as Integer, Float, Long, etc. The renderer that's defined for Number is a subclass of DefaultTableCellRenderer that simply sets its alignment value to RIGHT as we did in CurrencyRenderer. In other words, the Number renderer displays the toString() representation of the cell values, but it displays the text adjacent to the right side of the cell instead of the left (the default). An example of how this would appear if used with the Account Balance column in the SampleTableTest class is shown below:


Application output with Account Balance aligned right

javax.swing.ImageIcon
The renderer associated with this class allows you to display instances of ImageIcon within a table. The renderer is simply an instance of DefaultTableCellRenderer that takes advantage of the fact that a JLabel can contain both text and an icon. Instead of rendering the cell by setting its text value, this renderer sets its icon instead.

java.lang.Boolean
When this renderer is used, it displays the value for the cell as a JCheckBox that is either checked (when the cell's value is true) or unchecked (when the value is false). An example of how it would appear if used with the Gender column SimpleTableTest is shown below:


Application output with gender as a checkbox


Back to top


Editing table cells

Although each cell in the Gender column now appears to be a JComboBox, it's not possible to change the gender that's selected. In fact, none of the cells in the table is editable, and clicking on them merely causes the row to be selected. To change this behavior, you must override the isCellEditable() method, because the implementation in DefaultTableModel always returns false. However, this can be changed easily by adding the following code to TableValues:




public boolean isCellEditable(int row, int column) {
if (column == GENDER) {
return true;
}
return false;
}

This indicates that the cells in the Gender column are now editable. However, if you click on a cell in that column intending to select a gender from a JComboBox, you may be surprised to find that nothing happens except that the row you clicked on becomes selected. If you double-click on the cell, a JTextField appears that is initialized with the string equivalent of the cell's Boolean value ("true" or "false") and you can edit the data in the text field:


Application output with editable text field

You may be surprised that a text field appears when you edit the cell, because the cell seems to contain a JComboBox, but remember that table cells don't actually contain any components. The cells are simply drawn by components (the renderers), and in this case, the component happens to be a JComboBox. However, editing is a completely separate process that may or may not be handled by the same type of component that performed the rendering. For example, the default rendering component used by JTable is a JLabel, while the default editing component is a JTextField, which is why a text field appeared in this case.

Regardless of which type of component is used, it may seem that the cells are finally editable, which is partly true, but if you enter a value into one of these cells, the value you type is discarded once you complete the editing. To understand why this occurs and what to do about it, you should be familiar with cell editors and how JTable handles the editing of its cells.

Cell editors
Just as cell renderers control the way that a cell's values are drawn, cell editors handle cell value editing. Editors are slightly more complex than renderers, but have many similarities to renderers:

  • An editor can be assigned to one or more TableColumn instances.
  • An editor can be associated with one or more data types (classes), and will be used to display that type of data when no editor is associated with a cell's column.
  • Existing visual components are used to provide editing capabilities, just as they are used by renderers to draw cell values. In fact, the same type of visual component that's used as a cell's renderer is often used for its editor as well. For example, a cell might be assigned a renderer that uses a JComboBox and an editor that uses the same component.

You can assign an editor to one or more TableColumn instances or object types using the setCellEditor() method in TableColumn and setDefaultEditor() in JTable, respectively. However, the implementation of the TableCellEditor interface is more complex than TableCellRenderer, and to understand the methods defined in TableCellEditor, it's useful to examine how editors interact with JTable instances.

When a JTable detects a mouse click over one of its cells, it calls the isCellEditable() method in the TableModel. That method returns a value of false if the cell should not be editable, in which case processing terminates and no further action is taken. However, if the method returns true, then the table identifies the cell editor for that cell and calls the CellEditor's isCellEditable() method as well.

Although TableModel and CellEditor both define methods called isCellEditable(), there is an important difference between the two. Specifically, the TableModel method is only passed row and column index values, while the CellEditor method is also passed the EventObject representing the mouse click. This can be used, for example, to check the "click count" stored in the event. A cell must be double-clicked before it is edited, as observed earlier when editing the Gender column values. In other words, the isCellEditable() method returns a value of false when the click count is 1, while it returns true if the count is greater than 1. This behavior allows the cell editor to distinguish between a request to select the cell (a single-click) and a request to edit the cell (a double-click).

The edit operation is allowed to proceed only if both the TableModel's and the CellEditor's isCellEditable() method return a value of true. When that's the case, the editing is initiated by calling the getTableCellEditorComponent() method, which is passed the following parameters:

  • A reference to the JTable that contains the cell being edited
  • A reference to the cell's current value
  • A boolean flag that indicates whether or not the cell is selected
  • The row index of the cell being edited
  • The column index of the cell being edited

If these parameters look familiar, it's because they're almost identical to those passed to the getTableCellRendererComponent() method in TableCellRenderer. The only difference is that this method is not passed a boolean value indicating whether or not the cell has the input focus, since that is implied by the fact that the cell is being edited.

Before returning a reference to the component that's responsible for handling editing, getTableCellEditorComponent() should prepare the editor by initializing its value appropriately so that it matches the current cell value. For example, let's assume that we're creating an editor that allows users to select either "Male" or "Female" from a JComboBox that represents the Gender column value in TableValues. In that case, the JComboBox that performs the editing should be prepared by selecting the item it contains that corresponds to the cell's gender value: "Male" if the cell's value is true, "Female" if the value is false.

Once the editing component has been prepared and returned from the getTableCellEditorComponent() method, the JTable sets the size and location of that component so that it's directly "over" the cell being edited. This makes it appear that the cell is edited in place, when in fact, a component that supports editing (such as a JTextField or in this case, a JComboBox) has been superimposed over the cell.

With the editing component positioned over the cell being edited, the event that originally triggered the edit processing is posted to the editing component. For example, in the case of a JComboBox-based editor, the same mouse event that initiated the editing is passed to the combo box, possibly causing it to display its drop-down menu when editing starts. Finally, the CellEditor's shouldSelectCell() method is passed the same mouse event object, and if it returns true, the cell (and possibly others, depending upon the table's selection settings) is selected.

Each CellEditor is required to implement the addCellEditorListener() and removeCellEditorListener() methods, and the CellEditorListener interface defines two methods: editingStopped() and editingCanceled(). In practice, the only listener is usually the JTable itself, which is notified when editing is stopped or canceled. In addition, the CellEditor must implement the cancelCellEditing() and stopCellEditing() methods, which call the editingStopped() and editingCanceled() methods of registered listeners.

A request to end editing can come either from the JTable that contains the cell, or from the editor component itself. For example, suppose that you click on one cell and begin editing its value. If you then click on a different cell, the JTable calls the stopCellEditing() method of the first cell's editor before it initiates editing of the second cell. Alternatively, the editor component may stop the editing when some event occurs that implies that editing is complete. For example, when using a JComboBox as an editor, if it receives an ActionEvent message indicating that a selection was made, then it's appropriate to terminate the edit. Similarly, a JTextField might signal that editing has ended when it detects that the Return key was pressed.

Regardless of where the request originates to end editing, the JTable's editingStopped() method is called since it is a registered CellEditorListener. Inside this method, the table calls the editor's getCellEditorValue() method to retrieve the cell's new value and passes that value to the setValueAt() method in the JTable's TableModel. That is, it retrieves the cell's new value from the editor and sends it to the data model so that it can be stored "permanently."

The following class defines a component that can be used to provide editing of the rows in the Gender column defined in TableValues. It defines a subclass of JComboBox that initializes itself with "Male" and "Female" entries and listens for changes to its state (waits for a selection to be made).

When editing is initiated for one of the cells in the Gender column, the getTableCellEditorComponent() method is called, giving the editor a chance to initialize its state before it is made visible. In this case, the editor simply makes either "Male" or "Female" the selected entry based on the value stored in the cell being edited. When the user selects an item in the JComboBox, fireEditingStopped() is called, which signals to the table that the edit session has ended. The table will then call getCellEditorValue() to retrieve the new value that should be stored in the cell and will pass that value to the TableModel's setValueAt() method.




import java.awt.Component;
import java.util.EventObject;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class GenderEditor extends JComboBox implements TableCellEditor {

protected EventListenerList listenerList = new EventListenerList();
protected ChangeEvent changeEvent = new ChangeEvent(this);

public GenderEditor() {
super();
addItem("Male");
addItem("Female");
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
fireEditingStopped();
}
});
}

public void addCellEditorListener(CellEditorListener listener) {
listenerList.add(CellEditorListener.class, listener);
}

public void removeCellEditorListener(CellEditorListener listener) {
listenerList.remove(CellEditorListener.class, listener);
}

protected void fireEditingStopped() {
CellEditorListener listener;
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i++) {
if (listeners[i] == CellEditorListener.class) {
listener = (CellEditorListener) listeners[i + 1];
listener.editingStopped(changeEvent);
}
}
}

protected void fireEditingCanceled() {
CellEditorListener listener;
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i++) {
if (listeners[i] == CellEditorListener.class) {
listener = (CellEditorListener) listeners[i + 1];
listener.editingCanceled(changeEvent);
}
}
}

public void cancelCellEditing() {
fireEditingCanceled();
}

public boolean stopCellEditing() {
fireEditingStopped();
return true;
}

public boolean isCellEditable(EventObject event) {
return true;
}

public boolean shouldSelectCell(EventObject event) {
return true;
}

public Object getCellEditorValue() {
return new Boolean(getSelectedIndex() == 0 ? true : false);
}

public Component getTableCellEditorComponent(JTable table, Object
value,
boolean isSelected, int row, int column) {
boolean isMale = ((Boolean) value).booleanValue();
setSelectedIndex(isMale ? 0 : 1);
return this;
}

}


Now that the editor component has been defined, it needs to be associated with the Gender column, as shown in the following code:




public SimpleTableTest() {
Container pane = getContentPane();
pane.setLayout(new BorderLayout());
TableValues tv = new TableValues();
table = new JTable(tv);
TableColumnModel tcm = table.getColumnModel();
TableColumn tc = tcm.getColumn(TableValues.GENDER);
tc.setCellRenderer(new GenderRenderer());
tc.setCellEditor(new GenderEditor());
table.setDefaultRenderer(Float.class, new CurrencyRenderer());
JScrollPane jsp = new JScrollPane(table);
pane.add(jsp, BorderLayout.CENTER);
}

When this code is compiled and run, a JComboBox correctly appears, is initialized with the appropriate gender value, and allows you to select either "Male" or "Female":


Application output with gender value

However, selecting a different value from the one already stored in the cell does not result in the cell's value being modified. That's because the value is never changed in the TableModel, which is done by implementing the setValueAt() method in the TableValues class:




public void setValueAt(Object value, int row, int column) {
values[row][column] = value;
}



Back to top


DefaultCellEditor

It's not necessary in every case to build a completely new cell editor. In fact, the DefaultCellEditor class allows you to easily create editor components using a JCheckBox, JComboBox, or JTextField. All that's necessary is to create an instance of DefaultCellEditor and pass it an instance of one of these three components. However, the DefaultCellEditor is not very flexible, and you'll often need to create your own editor as was done in this case.



Back to top


Resources



Back to top