JClass LiveTable

PreviousNextIndex

4

Displaying and Editing Cells

Overview  Default Cell Rendering and Editing

Rendering Cells  Editing Cells  The JCCellInfo Interface


4.1 Overview

JClass LiveTable offers a flexible way to display and edit any type of data contained in a table's cells. The following sections explain the techniques for displaying and editing cells in your programs.

In order to display a cell, JClass LiveTable has to know what type of data renderer is associated with the cell so it knows how to paint that data into the cell area. Similarly, in order for users to edit the cell values, LiveTable has to know what editor to return for that data type.

These operations are performed using the classes in the JClass cell package, which is structured as follows:

JClass Cell Package

Contents

com.klg.jclass.cell

Contains editor/renderer interfaces and support classes, including these interfaces:

 

JCCellEditor: used to define an editor

JCCellRenderer: the common and basic interface for renderers

JCComponentCellRenderer: allows the creation of renderers that are based on JComponent

JCLightCellRenderer: allows the creation of renderers based on direct drawing

com.klg.jclass.cell.editors

Contains editors for common data types.

 

Please see Section 4.4.1, Default Cell Editors, for details.

com.klg.jclass.cell.renderers

Contains renderers for common data types, including expressions.

Please see Section 4.3.1, JClass Cell Renderers, for details.

com.klg.jclass.cell.validate

Contains data validation interfaces and support classes.

This JClass cell package is generic; renderers and editors written for JClass LiveTable will work with other JClass products. In addition, JClass Field components can work as renderers and editors within JClass LiveTable, allowing very lightweight operation.

Note: For the JClass Field component to work as a renderer, you need to use a particular instance from the com.klg.jclass.field.cell package.

JClass LiveTable has been designed to identify the type of data being retrieved from the data source and to provide the appropriate cell renderer and cell editor for that data type. For example, if JClass LiveTable encounters an expression in a cell (for example, any formula from com.klg.jclass.util.formulae), the default JCExpressionCellRenderer will be used.

Often, however, you will want to control the way data in a particular area of the table is rendered, or assign a specific type of editor for that data. An example of this is rendering String data in multiple lines and using javax.swing.JTextArea as the editor, rather than rendering and editing single line Strings.

The following sections describe the techniques for rendering and editing cells by beginning with the easiest default methods, followed by detailed explanations for setting specific renderers and editors, mapping renderers and editors to a particular data type, and creating your own renderers and editors.


4.2 Default Cell Rendering and Editing

Basic Editors and Renderers

When the table draws itself, it accesses the data source and attempts to paint the contents of each cell. In doing so, it works through a two-stage process:

  1. The table checks to see if a renderer has been assigned to the cell or a series of cells by the CellRenderer property in the cell's style.
  2. If the table can't find a specific CellRenderer for the data, it uses the default mapping for that data type.

The following table lists the cell renderers and editors for common data types included with JClass LiveTable, which are found in the com.klg.jclass.cell.renderers and com.klg.jclass.cell.editors packages, respectively. When going through the above steps, JClass LiveTable uses these default mappings.

Data Type

Renderer

Editor

Boolean

JCStringCellRenderer

JCBooleanCellEditor

Date

JCStringCellRenderer

JCDateCellEditor

Double

JCStringCellRenderer

JCDoubleCellEditor

Expression

JCExpressionCellRenderer

none

Float

JCStringCellRenderer

JCFloatCellEditor

Image

JCImageCellRenderer

none

Integer

JCStringCellRenderer

JCIntegerCellEditor

Object

JCStringCellRenderer

none

String

JCStringCellRenderer

JCStringCellEditor

Although these editors and renderers are included with JClass LiveTable, you might find that you need more control over the way data is displayed and edited than simply relying on these defaults. The following sections explain cell rendering and cell editing in detail.


4.3 Rendering Cells

Cell rendering is simply the way in which data is drawn into a cell. JClass LiveTable includes renderers that you can use in your table. Additionally, two rendering models, JCLightCellRenderer and JCComponentCellRenderer, are provided if you want to create your own renderer. Each model caters to different rendering needs.

More information about included renderers is found in the next section, and information about the two rendering models on which you can base customized renderers is found in Section 4.3.4, Creating your own Cell Renderers.

4.3.1 JClass Cell Renderers

As shown in the table above, JClass LiveTable maps standard data types to specific renderers when the program does not specify a renderer for that data type (either by setting for a series or mapping). This means that most tables are easily rendered without any special coding. The renderers are internally assigned. JClass LiveTable also contains several cell renderers for specific data types that you can set for a series (see Section 4.3.2, Setting a Cell Renderer for a Series) or as a mapping (see Section 4.3.3, Mapping a Data Type to a Cell Renderer). These cell renderers are described in the following table and all of them are in com.klg.jclass.cell.renderers package.

Name

Data Type

Description

JCCheckBoxCellRenderer

boolean

Defines a JCComponentCellRenderer object that paints boolean objects in a table cell using Swing's JCheckBox.

JCComboBoxCellRenderer

integer

Defines a JCComponentCellRenderer that paints integer objects in a table using Swing's JComboBox.

JCImageCellRenderer

image

Defines a JCLightCellRenderer object that paints Image objects in a table cell.

JCExpressionCellRenderer

expression

Defines the result of a formula (com.klg.jclass.util.formulae.Expression).

JCLabelCellRenderer

String and/or image

Defines a JCLabelCellRenderer object that uses Swing's JLabel to render cell contents.

JCRawImageCellRenderer

image

Defines a JCLightCellRenderer object that paints unconverted Image objects in a table cell (extends JCScaledImageCellRenderer).

JCScaledImageCellRenderer

image

Defines a JCLightCellRenderer object that paints scaled Image objects in a table cell.

JCStringCellRenderer

String, boolean, double, float, integer, object

Defines a JCLightCellRenderer object that can draw Strings.

JCWordWrapCellRenderer

String

Defines word-wrapping logic for multiline display of Strings in cells.

The default mappings and these special renderer classes should provide rendering for most data types. Few programmers work under ideal conditions, however, and you may need to extend the capability of these renderers. JClass LiveTable includes ways for you to customize cell rendering as described in Section 4.3.4, Creating your own Cell Renderers.

4.3.2 Setting a Cell Renderer for a Series

Often, the rows and columns that comprise a table are grouped by the type of data they contain. You may be creating an order form that has a product name (a String) in one column, a part number (an Integer) in another, and a check box (a special type of object) in the final column to indicate that you want that product. For example:

Contents

Product Name

Part Number

Order Checkbox

Data Type

String

Integer

Boolean

All of these columns take a different data type, so their data is all rendered differently. LiveTable will automatically detect the type of data found, and use one of the default renderers for that column (please see Section 4.2, Default Cell Rendering and Editing, for a list of default renderers). However, you can use your own renderer if the default does not suit your needs.

In the case of the Order check box, the default renderer for its Boolean data type will be the JCStringCellRenderer. With this default renderer, since the data type is boolean, instead of having a check (or no check) painted onto the cell, "true" or "false" will appear. This is not desirable, so you need to deviate from JClass LiveTable's default renderer.

To set a new cell renderer for a range of cells, use a cell style, which has its own cell renderer property (for more information, please refer to Cell Styles, in Chapter 2). Inserting these lines of code into your program will do this:

  CellStyleModel style = table.getUniqueCellStyle(0,3);
  style.setCellRenderer(new JCCheckBoxCellRenderer());
  table.setCellStyle(JCTableEnum.ALL, 3, style);

The JCCheckBoxCellRenderer class defines an object that paints boolean objects in a table cell as checks. This way, the first two columns render automatically with the defaults, and the third column will use your defined renderer.

4.3.3 Mapping a Data Type to a Cell Renderer

Even though you can set the renderer series, your table may be designed in such a way that the data types within a row or column are not consistent, or will change depending on the data source. In this case you could decide not to set the renderer series at all, and allow the container to evaluate the data type and provide the appropriate renderer. Unfortunately, this means you have to use the default renderers for a given data type.

To use your own renderers without sacrificing flexibility, you can create a mapping. The mapping takes a data type and associates it with a JCCellRenderer object; whenever the container encounters that type of data, it uses the mapped JCCellRenderer object to render the data object in the cell.

Mapping a JCCellRenderer object to a data type takes the following construction:

  table.setCellRenderer(Class cellType, Class renderer);

For example, in the following code fragment (from TriangleTable.java in the examples/table/cell directory of the JClass distribution), the cell renderer is set for a particular data type, defined by java.awt.Polygon.

  try {
    table.setCellRenderer(Class.forName("java.awt.Polygon"),
    Class.forName
    ("examples.table.cell.TriangleCellRenderer"));

  ....

  }
  catch (ClassNotFoundException e) {
    e.printStackTrace(System.out);
    }
  }

The table.setCellRenderer() method takes a class to define the data type and a class to define the renderer. In the case below, we have created a class called TriangleCellRenderer, which is identified using the Class.forName() method imported from java.lang.Class. (Creating your own cell renderers is explained in the next section.)

Normally, you would use these mappings in a construction that would test for the presence of the renderer you specify, and throw an exception if the renderer class was not found, as is the case in the above sample.

To "unmap" a renderer, set the renderer class parameter to null.

Alternatively, you can map a particular cell renderer instance to a data type using:

  table.setCellRenderer(Class cellType, JCCellRenderer renderer);

This method is useful if you want to reuse the same renderer instance, or if your renderer does not have a default construction.

4.3.4 Creating your own Cell Renderers

Naturally, the renderer classes provided with JClass LiveTable will not meet every programmer's specific needs. However, they can be convenient as bases for creating your own renderer objects by subclassing the original classes. If you want to create your own renderer classes, you can build your own renderer from scratch. Both techniques are discussed below.

The examples/table/cell directory and the demos/table directories of your JClass LiveTable distribution contain a wide array of sample programs that use different approaches to cell rendering. You can use these examples and demos to help you refine your own renderers for whatever purpose you require.

Subclassing the Default Renderers

A simple way to create your own renderer objects is to subclass one of the renderers provided with JClass LiveTable. For example, CurrencyRenderer.java, found in the examples/table/cell directory, is an example of subclassing from the JCStringCellRenderer in the com.klg.jclass.cell.renderers package:

import com.klg.jclass.cell.renderers.JCStringCellRenderer;
import com.klg.jclass.cell.JCCellInfo;

import java.awt.Graphics;

public class CurrencyRenderer extends JCStringCellRenderer {

public void draw(Graphics gc, JCCellInfo cellInfo,
         Object o, boolean selected) {
  if (o instanceof Double) {
    double d = ((Double)o).doubleValue();
    o = formatLabel(d, 2);
  }
  super.draw(gc, cellInfo, o, selected);
}

Creating a Drawing-based Cell Renderer with JCLightCellRenderer

One way JClass LiveTable lets you write your own cell renderer is with JCLightCellRenderer. This model is used for drawing directly into a cell, which is ideal for custom painting and rendering text.

To create a drawing-based renderer object of your own, you must implement com.klg.jclass.cell.JCLightCellRenderer:

public interface JCLightCellRenderer {
public void draw(Graphics gc, JCCellInfo cellInfo, Object o,
  boolean selected);

public Dimension getPreferredSize(Graphics gc, JCCellInfo cellInfo,
  Object o);

}

The JCLightCellRenderer interface requires that you create two methods:

  1. A draw() method, which is passed a JCCellInfo object (see Section 4.5, The JCCellInfo Interface, for more details) containing information from the container about the cell, a java.awt.Graphics object, and the object to be rendered. The Graphics object is positioned at the origin of the cell (0,0), but is not clipped.
  2. A getPreferredSize() method, which is used to allow the renderer to influence the container's layout. The container may not honor the renderer's request, depending on a number of factors.

The following code, TriangleCellRenderer.java, draws a triangle into the cell area:

import java.awt.Polygon;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import com.klg.jclass.cell.JCCellInfo;
import com.klg.jclass.cell.JCLightCellRenderer;

public class TriangleCellRenderer implements JCLightCellRenderer {

public void draw(Graphics gc, JCCellInfo cellInfo,
Object o, boolean selected) {
  Polygon p = makePolygon(o);
  gc.setColor(selected ? cellInfo.getSelectedForeground()
    :cellInfo.getForeground());
  gc.fillPolygon(p);
}

public Dimension getPreferredSize(Graphics gc, JCCellInfo cellInfo, Object o) {
  // Make a polygon from the object
  Polygon p = makePolygon(o);
  // Return no size if no polygon was created
  if (p == null) {
    return new Dimension(0,0);
  }
  // Bounds of the polygon determine size
  Rectangle r = p.getBoundingBox();
  return new Dimension(r.x+r.width,r.y+r.height);
}

private Polygon makePolygon(Object o) {
  if (o == null) return null;
  if (o instanceof Number) {
    return makePolygon(((Number)o).intValue());
  }
  else if (o instanceof Polygon) {
    return (Polygon)o;
  }
  return null;
}


public Polygon makePolygon(int s) {
  Polygon p = new Polygon();
  p.addPoint(0,0);
  p.addPoint(0,s);
  p.addPoint(s,0);
  return p;
}
}

The above program creates a triangle renderer object that can handle both Integer and Polygon objects.

As required by JCCellRenderer, the program contains a draw() method in the lines:

public void draw(Graphics gc, JCCellInfocellInfo,
    Object o boolean selected) {

Polygon p = makePolygon(o);
gc.getColor(selected ? cellInfo.getSelectedForeground():
    cellInfo.getForeground());

gc.fillPolygon(p);
}

The draw() method renders the object o by making it into a polygon and drawing the polygon using the gc provided. Table, as the container, automatically translates and clips the gc, draws in the background of the cell, and sets the foreground color.

The parameter cellInfo can be used to retrieve other cell property information through the JCCellInfo interface (see Section 4.5, The JCCellInfo Interface).

The second required method, getPreferredSize(), is provided in the lines:

  public Dimension getPreferredSize(Grahpics gc, JCCellInfo cellInfo,
                    Object o) {
  Polygon p = makePolygon(o);
  if (p == null) {
    return new Dimension(0,0);
  }
  Rectangle r = p.getBoundingBox();
  return new Dimension(r.x+r.width,r.y+r.height);
  }

Here, the object is used to create a polygon (using a local method called makePolygon()). If it doesn't create a polygon from the object, the object is deemed to have no size (0,0) and will not be displayed by the renderer. If a polygon was created from the object, the polygon's bounds determine the size of the rectangle in the drawing area of the cell. The size returned is only a suggestion; control of the cell size can be overridden by the Table container.

Creating a Component-based Cell Renderer with JCComponentCellRenderer

While JCLightCellRenderer is useful for drawing directly into cells (that is, text rendering and custom cell painting), it is a cumbersome model to use if you want to draw a component as part of an editor/renderer pair. For example, if you wanted to use a drop-down list in a table cell, creating a renderer based on JCLightCellRenderer forces you to write the code that draws the arrow button. Obviously, it is more desirable to use the actual code for the component - this is exactly for what JCComponentCellRenderer is best suited.

Component-based cell renderers use an existing lightweight component for rendering the contents of a cell. As such, the JCComponentCellRenderer interface can be used to create a component-based cell renderer:

public interface JCComponentCellRenderer extends JCCellRenderer {
public Component getRendererComponent(JCCellInfo cellInfo, Object o,
    boolean selected);

}

The getRendererComponent() method returns the component that is to be used to render the cell. It is the responsibility of the implementor to use the information provided by getRendererComponent() to set up the component for rendering:

As an example, consider JCLabelCellRenderer.java from com.klg.jclass.cell.renderers, which uses a Swing JLabel for rendering String data.

import com.klg.jclass.cell.JCComponentCellRenderer;
import com.klg.jclass.cell.JCCellInfo;
import javax.swing.JLabel;
import java.awt.Component;

public class JCLabelCellRenderer extends JLabel
implements JCComponentCellRenderer {

public JCLabelCellRenderer() {
  super();
}

public Component getRendererComponent(JCCellInfo cellInfo, Object o,
                    boolean selected) {

  if (o != null) {
    if (o instanceof String) {
      setText((String)o);
    }
    else {
      setText(o.toString());
    }
  }
  else {
    setText("");
  }
  setFont(cellInfo.getFont());
  setBackground(selected ? cellInfo.getSelectedBackground() :
                cellInfo.getBackground());

  setForeground(selected ? cellInfo.getSelectedForeground() :
                cellInfo.getForeground());

  setHorizontalAlignment(cellInfo.getHorizontalAlignment());
  setVerticalAlignment(cellInfo.getVerticalAlignment());
  return this;
  }
  }

In this example, note that JCLabelCellRenderer extends JLabel, which makes it easier for the renderer to control the label's appearance.

In getRendererComponent(), the object o is converted to a String and used to set the Text property of the label. Then, the font, foreground color, and background color are extracted from the cellInfo. Finally, the JLabel instance is passed back to the container.

JCComponentCellRenderer is a very powerful rendering model. While it is not as flexible as JCLightCellRenderer, it allows the reuse of code by using a lightweight component as a rubber stamp for painting in a cell. Any existing lightweight container can be used to render data inside of a cell - even other JClass components.


4.4 Editing Cells

While rendering cells is fairly straightforward, handling interactive cell editing is considerably more complex. Cell editing involves coordinating the user-interactions that begin and end the edit with cell data validation and connections to the data source. In JClass, cell editing is handled using the JCCellEditor interface.

A typical cell edit works through the following process:

Because cell editing has been designed to be flexible, you can have as little or as much control over the editing process as you want. The following sections explain cell editing in further detail.

4.4.1 Default Cell Editors

Cell editors are typically Swing components with extended functionality provided by the com.klg.jclass.table.cell.JCCellEditor interface. Although every data object is guaranteed to have a cell renderer, not every object is guaranteed to have an editor. Unless an object has an editor, the cell is not editable, regardless of whether the table.setEditable() method has a true value for that cell. Most of the standard data types have default editors which are internally associated with that data type. If the program does not specify an editor for a series or map a data type to an editor, the Table uses the default. The following editors are provided in the com.klg.jclass.cell.editors package:

Editor

Description

BaseCellEditor

Provides a base editing component for other editors.

JCBigDecimalCellEditor

An editor using a simple text field for BigDecimal objects.

JCBooleanCellEditor

Provides a simple text editing component that allows the user to set the boolean value as true, false, t, or f.

JCByteCellEditor

An editor using a simple text field for Byte objects.

JCCheckBoxCellEditor

An editor for boolean data that automatically changes the checked state.

JCComboBoxEditor

An editor using a simple Swing JComboBox for editing an enum.

JCDateCellEditor

An editor using a simple text field for Date objects

JCDoubleCellEditor

An editor using a simple text field for Double objects.

JCFloatCellEditor

An editor using a simple text field for Float objects.

JCImageCellEditor

An editor using a simple text field for Image objects.

JCIntegerCellEditor

An editor using a simple text field for Integer objects.

JCLongCellEditor

An editor using a simple text field for Long objects.

JCMultilineCellEditor

A simple text editing component for multiline data.

JCShortCellEditor

An editor using a simple text field for Short objects.

JCSqlDateCellEditor

An editor using a simple text field for SQL Date objects.

JCSqlTimeCellEditor

An editor using a simple text field for SQL Time objects.

JCSqlTimestampCellEditor

An editor using a simple text field for SQL Timestamp objects.

JCStringCellEdtitor

Provides a simple text editing component.

JCWordWrapCellEditor

Provides a simple text editing component that wraps text.

While these classes provide editing capability for most data types, many real-world situations require greater control over cell editing, editing components, and their relationships to specific data types. The following sections explore how you can more minutely control the cell editing mechanism in your programs.

4.4.2 Setting a Cell Editor for a Series

As mentioned above, JClass LiveTable contains logic that will map data types to their default editors. If you want to override these defaults, you can set a specific editor for a series of cells in your table by setting the CellEditor property on a cell style, for a range of cells:

  CellStyleModel style = table.getUniqueCellStyle(0,3);
  style.setCellEditor(new JCStringCellEditor());
  table.setCellStyle(JCTableEnum.ALL, 3, style);

This code uses the same CellEditor (the default String editor in the com.klg.jclass.cell.editors package) for all of the cells in the fourth column in the table.

4.4.3 Mapping a Data Type to a Cell Editor

Even though you can set the editor series, your table may be designed in such a way that the data types within a row or column are not consistent, or will change depending on the data source. In this case you can create a mapping. The mapping takes a data type and associates it with a cell editor; whenever the container encounters that type of data, it uses the mapped JCCellEditor.

Mapping a CellEditor object to a data type takes the following construction:

  table.setCellEditor(Class cellType, Class Editor);

Consider the following sample from TriangleTable.java in the examples/table/cell directory of the JClass LiveTable distribution:

  try {
   table.setCellEditor(Class.forName("java.awt.Polygon"),
   Class.forName
   ("examples.table.cell.TriangleCellEditor"));
  }
  catch (ClassNotFoundException e) {
   e.printStackTrace(System.out);
  }

The table.setCellEditor() method takes a class to define the data type and a class to define the editor. In the case above, we have created a class called TriangleCellEditor, which is identified using the Class.forName() method imported from java.lang.Class. (Creating your own cell editors is explained in the next section).

To "unmap" an editor, set the editor class parameter to null.

Alternatively, you can map a cell editor to a data type using:

table.setCellEditor(Class cellType, JCCellEditor editor);

This method is useful if you want to reuse the same editor instance, or if your editor does not have a default constructor.

Note: If the value for a particular cell is null, JClass LiveTable has no way of determining its type. This can cause problems if mapping a null value to an editor. To work around this, use the DataType property that is used with cell styles. LiveTable refers to DataType when it encounters a null in the data source.

4.4.4 Creating Your Own Cell Editors

To create a cell editor object, you must implement the com.klg.cell.JCCellEditor interface. The following code comprises the JCCellEditor interface:

  public interface CellEditor extends JCCellEditorEventSource,
        serializable{

  public void initialize(AWTEvent ev, JCCellInfo info, Object o);
  public Component getComponent();
  public Object getCellEditorValue();
  public boolean stopCellEditing();
  public boolean isModified();
  public void cancelCellEditing();
  public JCKeyModifier[] getReservedKeys();
  }

This chart describes each of the methods in JCCellEditor:

Method and Description

public void initialize(AWTEvent ev, JCCellInfo info, Object o);

 

The table calls initialize() before the edit starts to let the editor know what kind of event started the edit, using java.awt.AWTEventObject. The size of the cell comes from the JCCellInfo interface (detailed below). The initialize() method also provides the data object (Object o).

public Component getComponent();

 

Returns the AWT component that does the editing. The component should be lightweight.

public Object getCellEditorValue();

 

Returns the value contained in the editor. This method is called by the table when the edit is complete. The value will be sent to the data source.

public boolean stopCellEditing();

 

When this method is called by the table, the editor can refuse to commit invalid values by returning false. This tells the container that the edit is not valid.

public boolean isModified();

 

The container uses this method to check whether the data has changed. This can save unnecessary access to the data source when the data has not actually changed.

public void cancelCellEditing();

 

Called by the table to stop editing and restore the cell's original contents.

public JCKeyModifier[] getReservedKeys();

 

Retrieves the keys the editor would like to reserve for itself. In order to avoid the container overriding key processing in the editor, the editor can pass back a list of keys it wishes to reserve. The container can refuse the editor's request to reserve keys. Most editors can simply return null for this method.

Because the JCCellEditor interface extends JCCellEditorEventSource, the following two methods are required to manage JCCellEditor event listeners:

Method and Description

public abstract void addCellEditorListener(JCCellEditorListener l);

 

Adds a listener to the list that's notified when the editor starts, stops, or cancels editing.

public abstract void removeCellEditorListener(JCCellEditorListener l);

 

Removes the listener.

In addition to implementing the methods of JCCellEditor, an editor is responsible for monitoring events and sending editingStopped and editingCanceled events to the table. This functionality is further explained in Creating Your Own Cell Editors.

Subclassing the Default Editors

One easy way to create your own editor is to subclass one of the editors provided in the com.klg.jclass.cell.editors package. The following code is from examples/table/cell/MoneyCellEditor.java. It creates a simple editor that extends the JCStringCellEditor class. The MoneyCellEditor class formats the data as money (two digits to the right of the decimal point) instead of a raw String; but JCStringCellEditor does most of the work.

The initialize() method in MoneyCellEditor takes the object passed in and creates a Money value for it. The getCellEditorValue() method will pass the Money value back to the container.

import java.awt.Dimension;
import com.klg.jclass.cell.editors.JCStringCellEditor;
import com.klg.jclass.cell.JCCellInfo;
import java.awt.AWTEvent;

public class MoneyCellEditor extends JCStringCellEditor {

Money initial = null;

public void initialize(AWTEvent ev, JCCellInfo info, Object o) {
  if (o instanceof Money) {
    Money data = (Money)o;
    initial = new Money(data.dollars, data.cents);
  }
  super.initialize(ev, info, initial.dollars+"."+initial.cents);
}
public Object getCellEditorValue() {
  int d, c;
  String text = getText().trim();
  Money new_data = new Money(initial.dollars, initial.cents);

  try {
    // one of these will probably throw an exception if
    // the number format is wrong
    d = Integer.parseInt(text.substring(0,text.indexOf('.')));
    c = Integer.parseInt(text.substring(text.indexOf('.')+1));

    new_data.setDollars(d);
    // this will throw an exception if there's an invalid
    // number of cents
    new_data.setCents(c);
  }
  catch(Exception e) {
    return null;
  }

  return new_data;
}

public boolean isModified() {
  if (initial == null) return false;
  Money nv = (Money)getCellEditorValue();
  if (nv == null) return false;
  return (initial.dollars != nv.dollars || initial.cents != nv.cents);
}
}

Starting with one of the cell editors provided with the com.klg.cell.editors package can save you a lot of work coding entire editors on your own.

Writing Your Own Editors

Of course, you may not want to subclass any of the editors provided with the com.klg.jclass.cell.editors package. The following is from an editor that was written without subclassing an existing editor. By implementing the JCCellEditor interface, we have written an editor that will edit triangles. The code is in examples/table/cell/TriangleCellEditor.java. You can see it work by running examples.table.cellTriangeTable.

The editor handles both Integer and Polygon data types. It initializes the editor with the object to be edited, either a Number or a Polygon:

  ....

  public void initialize(AWTEvent ev, CellInfo info, Object o) {
   if (o instanceof Polygon) {
   orig_poly = (Polygon)o;
   }
   else if (o instanceof Number) {
   // Create polygon from the number
   int s = ((Number)o).intValue();
   orig_poly = new Polygon();
   orig_poly.addPoint(0,0);
   orig_poly.addPoint(0,s);
   orig_poly.addPoint(s,0);
   }
  
   new_poly = null;
  
   margin = info.getMarginSize();
  }
  

The editor also needs to retrieve the AWT component that will be associated with it. In this case the editor is an a javax.swing.JComponent object.

  ....
  public Component getComponent() {
   return this;
  }

The isModified() method checks to see if the editor has changed the data, and getCellEditorValue() which returns the new Polygon created.

  ....
  public boolean isModified() {
   return new_poly != null;
  }
  
  public Object getCellEditorValue() {
   return new_poly;
  }
  

The JCCellEditor interface defines the stopCellEditing() method, which stops and commits the editing operation. In the case of this example, there isn't any validation taking place, so the stopCellEditing() method will be unconditionally obeyed. The TriangleCellEditor also defines a cancelCellEditing() method, which resets the new Polygon.

  ....
  public boolean stopCellEditing() {
   return true;
  }
  
  public void cancelCellEditing() {
   new_poly = null;
   return;
  }

The editor contains a local method for retrieving a non-null polygon for drawing:

  ....
  private Polygon getDrawPoly() {
   if (new_poly == null)
   return orig_poly;
   return new_poly;
  }

The editor also has to determine the minimum size for the cell.

  ....
  public Dimension minimumSize() {
   Rectangle r = getDrawPoly().getBoundingBox();
   return new Dimension(r.width+r.x,r.height+r.y);
  }

Finally, the editor needs to know how to paint the current polygon into the cell:

  ....
public void paintComponent(Graphics gc) {
  // No L&F, so paint your own background.
  if (isOpaque()) {
    if (!gc.getColor().equals(getBackground())) {
      gc.setColor(getBackground());
    }
    Rectangle r = getBounds();
    gc.fillRect(0, 0, r.width, r.height);
  }

  int x, y;

  Polygon local_poly = getDrawPoly();
  gc.setColor(cellInfo.getForeground());
  gc.translate(margin.left, margin.top);
  gc.fillPolygon(local_poly);
  
  for(int i = 0; i < local_poly.npoints; i++) {
    x = local_poly.xpoints[i];
    y = local_poly.ypoints[i];
    gc.drawOval(x-2,y-2,4,4);
  }
  
  gc.translate(-margin.left, -margin.top);
}

Much of the rest of the editor handles mouse events to drag the triangle points, or to move the whole triangle inside the cell. See the example file for this code.

Finally, the editor contains event listener methods that add and remove listeners from the listener list. These listeners are notified when the editor starts, stops, or cancels an edit.

  JCCellEditorSupport support = new JCCellEditorSupport();
  ....
  public void addCellEditorListener(CellEditorListener l) {
   support.addCellEditorListener(l);
  }
  
  public void removeCellEditorListener(CellEditorListener l) {
   support.removeCellEditorListener(l);
  }

Note that an instance of com.klg.jclass.cell.JCCellEditorSupport is used to manage the listener list. JCCellEditorSupport is a useful convenience class for editors that want to send events to JClass LiveTable programs.

The TriangleCellEditor is an example of a fairly complex implementation of the JCCellEditor interface. It contains all of the core methods of the interface, and extends the capabilities for an interesting type of data. You can use this example to help you to write your own JCCellEditor classes that handle any type of data you care to display and edit.

Handling Editor Events

The com.klg.jclass.cell package contains several event and listener classes that enable cell editors and their containers to inform each other of changes to the cell contents, and allow you to control validation of the cell's edited contents.

The simplest way to handle JCCellEditor events is to use the JCCellEditorSupport convenience class. JCCellEditorSupport makes it easy for cell editors to implement standard editor event handling by registering event listeners and providing easy methods for sending events.

JCCellEditorSupport methods include:

Method

Description

addCellEditorListener()

Adds a new JCCellEditorListener to the listener list

removeCellEditorListener()

Removes a JCCellEditorListener from the list

fireStopEditing()

Sends an editingStopped event to all listeners

fireCancelEditing()

Sends an editingCanceled event to all listeners

For example, consider the TriangleCellEditor. The changes made are not actually sent to the data source until the user clicks on another cell. It is more useful to have the editor send an editingStopped event when the mouse button is released:

  public void mouseReleased(MouseEvent e) {
   support.fireStopEditing(new JCCellEditorEvent(e));
  }

For more complete control, however, you will have to use the other event handling classes provided in the com.klg.jclass.cell package:

Method

Description

JCCellEditorEvent

Sent when the JCCellEditor finishes an operation. The JCCellEditorEvent contains the event that originated the operation in the editor.

JCCellEditorListener

The container registers a JCCellEditorListener to let the JCCellEditor inform it when editing has stopped or been canceled.

JCCellEditorEventSource

This class defines the add and remove methods for an object that posts JCCellEditorEvents.

Editor Key Control

Sometimes, you may want your cell editor to be able to accept keystrokes that have already been reserved for a specific purpose in the container (a Tab key in LiveTable, for example). To do this, you need to use the JCKeyModifier class to reserve a key/modifier combination:

  JCKeyModifier(int key, int modifier, boolean canInitializeEdit);

Using this class, you can reserve a key for a particular modifier or for all modifiers. To reserve Ctrl-Tab and Shift-Tab you would specify two JCKeyModifier objects with standard KeyEvent modifiers, for example KeyEvent.ALT_MASK.

If you want to reserve all Tab keys for the editor, you can use either of the following:

Note that the container can still choose to ignore reserved keys.


4.5 The JCCellInfo Interface

You can see that JCComponentCellRenderer, JCLightCellRenderer and JCCellEditor use the JCCellInfo interface to get information about the cell. The JCCellInfo interface provides information about how the container wants to show the cell. The renderer and editor determine whether or not to honor the container's request.

The JCCellInfo interface gives the renderer and editor access to cell formatting information from the Table, including:

This information is fairly generic. The com.klg.jclass.table package also contains an object called TableCellInfoModel, which extends JCCellInfo to include more detailed information from the Table. TableCellInfoModel is useful for retrieving Table-specific information for use in the editor or renderer.

Note that editors and renderers that rely on TableCellInfoModel can only be used with JClass LiveTable.

Figure 12 :  The relationship of border sides, margins, and drawing
area provided by JCCellInfo.

For more information, please see the JCCellInfo API documentation.


PreviousNextIndex