![]() ![]() ![]() |
4
Displaying and Editing Cells
Overview
Default Cell Rendering and Editing
Rendering Cells
Editing Cells
The JCCellInfo Interface
4.1 Overview
JClass HiGrid offers a flexible way to display and edit any type of data contained in its cells. The following sections explain the techniques for displaying and editing cells in your programs.
In order to display a cell, JClass HiGrid 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, HiGrid 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
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 onJComponent.
JCLightCellRenderer
: Allows the creation of renderers based on direct drawing.Contains editors for common data types.
Please see Section 4.4.1, Default Cell Editors, for details.
Contains renderers for common data types.
Please see Section 4.3.1, JClass Cell Renderers, for details.
This JClass cell package is generic; renderers and editors written for JClass HiGrid will work with other JClass products. In addition, JClass Field components can work as renderers and editors within HiGrid, allowing very lightweight operation.
JClass HiGrid 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. Often, however, you will want to control the way data in a particular area of the grid 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 grid draws itself, it accesses the data source and attempts to paint the contents of each cell. In doing so, it:
The following table lists the cell renderers and editors for common data types included with JClass HiGrid, which are found in the
com.klg.jclass.cell.renderers
andcom.klg.jclass.cell.editors
packages, respectively. When going through the above steps, JClass HiGrid uses these default mappings.Although these editors and renderers are included with JClass HiGrid, 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 HiGrid includes renderers that you can use in your grid. Additionally, two rendering models,
JCLightCellRenderer
andJCComponentCellRenderer
, are provided if you want to create your own renderer. Each model caters to different rendering needs.More information about included renderers are 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.3, Creating your own Cell Renderers.
4.3.1 JClass Cell Renderers
As shown in the table above, JClass HiGrid maps standard data types to specific renderers when the program does not specify a renderer for that data type. This means that most grids are easily rendered without any special coding. The renderers are internally assigned.
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 HiGrid includes ways for you to customize cell rendering, as described in Section 4.3.3, Creating your own Cell Renderers.
4.3.2 Mapping a Data Type to a Cell Renderer
Because there are many different data types within a row, JClass HiGrid creates a mapping between data types and cell renderers. The mapping takes a data type and associates it with a cell renderer; whenever the container encounters that type of data, it uses the mapped
JCCellRenderer
. This mapping is performed automatically for standard JDBC data types.Mapping a
thisCell.CellFormat.setCellRendererName(String cellType);JCCellRenderer
object to a data type takes the following construction, wherecellType
is the cell renderer andthisCell
is the current cell: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 class was not found.
It is possible to use a new feature of the
EditorRendererRegistry.getCentralRegistry().addClass(com.klg.jclass.cell
package, called theEditorRendererRegistry
, to associate a data type with an editor and a renderer. Imagine a case where you have a column of Boolean quantities. You wish to allow your users to edit a cell by typing a zero or a one, but you wish to display the result in a checkbox. The following code associates the desired editor and renderer with the underlying data type:
"java.lang.Integer",
null,
"com.klg.jclass.cell.editors.JCIntegerCellEditor",
"com.klg.jclass.cell.renderers.JCCheckBoxCellRenderer");JClass HiGrid will now use these editors/renderers for integer types.
Note however that a mapping via the central registry is global, in the sense that it will be used by all the JClass components in your application that use editors and renderers.
See the JCLASS_HOME/examples/higrid/data example for changing editors and renderers on a per-column basis. To see how to override the default associations between data types and editors/renderers, see
EditorRendererExample
in the JCLASS_HOME/examples/higrid/visual directory.
4.3.3 Creating your own Cell Renderers
Naturally, the renderer classes provided with JClass HiGrid 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.
Subclassing the Default Renderers
A simple way to create your own renderer objects is to subclass one of the renderers provided with JClass HiGrid. For example, CurrencyRenderer.java is an example of subclassing from the
import com.klg.jclass.cell.renderers.JCStringCellRenderer;JCStringCellRenderer
in thecom.klg.jclass.cell.renderers
package:
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 HiGrid 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
public interface JCLightCellRenderer {com.klg.jclass.cell.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:
- A
draw()
method, which is passed aJCCellInfo
object (see Section 4.5, The JCCellInfo Interface, for more details) containing information from the container about the cell, ajava.awt.Graphics
object, and the object to be rendered. TheGraphics
object is positioned at the origin of the cell (0,0), but is not clipped.- 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
andPolygon
objects.As required by
public void draw(Graphics gc, JCCellInfocellInfo, Object o booleanJCCellRenderer
, the program contains adraw()
method in the lines:
selected) {
Polygon p = makePolygon(o);
gc.getColor(selected ? cellInfo.getSelectedForeground():
cellInfo.getForeground());
gc.fillPolygon(p);
}The
draw()
method renders the objecto
by making it into a polygon and drawing the polygon using thegc
provided. The grid, as the container, automatically translates and clips thegc
, 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 theJCCellInfo
interface (see Section 4.5, The JCCellInfo Interface).The second required method,
public Dimension getPreferredSize(Grahpics gc, JCCellInfo cellInfo,getPreferredSize()
, is provided in the lines:
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 grid container.Creating a Component-based Cell Renderer with JCComponentCellRenderer
While
JCLightCellRenderer
is useful for drawing directly into cells (i.e. 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 dropdown list in a grid cell, creating a renderer based onJCLightCellRenderer
forces you to write the code that draws the arrow button. Obviously, it is more desirable to use the actual code for the component itself, for which is exactly whatJCComponentCellRenderer
is best suited.Component-based cell renderers use an existing lightweight component for rendering the contents of a cell. As such, the
public interface JCComponentCellRenderer extends JCCellRenderer {JCComponentCellRenderer
interface can be used to create a component-based cell renderer:
public Component getRendererComponent(JCCellInfo cellInfo, Object o,
boolean selected);
}The
getRendererComponent
returns the component that is to be used to render the cell. It is the responsibility of the implementor to use the information provided bygetRendererComponent
to set up the component for rendering:
cellInfo
contains information from the container about the cell (see Section 4.5, The JCCellInfo Interface for more details).o
is the object to be rendered.selected
is a Boolean indicating whether the cell is selected. Many implementors use this information to modify the component appearance.As an example, consider JCLabelCellRenderer.java from
import com.klg.jclass.cell.JCComponentCellRenderer;com.klg.jclass.cell.renderers
, which uses a SwingJLabel
for rendering String data.
import com.klg.jclass.cell.JCCellInfo;
import javax.swing.JLabel;
import javax.swing.JComponent;
public class JCLabelCellRenderer extends JLabel
implements JCComponentCellRenderer {
public JCLabelCellRenderer() {
super();
}
public JComponent getRendererComponent(JCCellInfo cellInfo,
Object o,boolean selected) {
if (o != null) {
if (o instanceof String) {
setText((String)o);
}
else {
setText(o.toString());
}
}
else {
setText("");
}
setBackground(cellInfo.getBackground());
setForeground(cellInfo.getForeground());
return this;
}
}In this example, note that
JCLabelCellRenderer
extendsJLabel
, which makes it easier for the renderer to control the label's appearance.In
getRendererComponent()
, objecto
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 thecellInfo
. Finally, theJLabel
instance is passed back to the container.
JCComponentCellRenderer
is a very powerful rendering model. While it is not as flexible asJCLightCellRenderer
, it allows the reuse of code by using a lightweight component as a rubberstamp 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:
- The container listens for events that come from the editor by implementing
JCCellEditorListener
.- When a user initiates a cell edit with either a mouse click or a key press, the container calls
JCCellEditor.initialize()
and passes aJCCellInfo
object with information about the cell, and the object (data) that will be edited.- The
JCCellEditor
displays the data and changes it according to user input.- If the user traverses out of the cell, then the container calls the
stopCellEditing()
method, which asks theJCCellEditor
to validate the edit. If the edit is not valid (that is,stopCellEditing()
returnsfalse
) the container then retrieves the original cell value from the data source. If the edit is valid, then the container callsgetCellEditorValue()
on the editor to retrieve the new value of the cell and send it to the data source.- If the user types a key that the editor interprets as "done" (for example, Enter), the editor will inform the grid that the edit is complete by sending an
editingStopped
event to the grid. Typical editors will validate the user's changes before sending the event.- If the user types a key that the editor interprets as "cancel" (for example, Esc), the editor will instruct the grid to cancel the edit by sending an
editingCanceled
event.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
The following editors are provided in the
com.klg.jclass.cell.editors
package: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 Mapping a Data Type to a Cell Editor
It is likely that your grid is designed in such a way that there are many different data types within a row. In this case HiGrid creates a mapping between data types and cell editors. 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
thisCell.CellFormat.setCellEditorName(String cellType);JCCellEditor
object to a data type takes the following construction, wherecellType
is the cell editor andthisCell
is the current cell:Normally, you would use these mappings in a construction that would test for the presence of the editor you specify, and throw an exception if the class was not found.
4.4.3 Creating Your Own Cell Editors
To create a cell editor object, you must implement the
public interface JCCellEditor extends JCCellEditorEventSource, serializable{com.klg.cell.JCCellEditor
interface. The following code comprises theJCCellEditor
interface:
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();
}Consider each of the methods in
JCCellEditor
:Because the
JCCellEditor
interface extendsJCCellEditorEventSource
, the following two methods are required to manageJCCellEditor
event listeners:In addition to implementing the methods of
JCCellEditor
, an editor is responsible for monitoring events and sendingeditingStopped
andeditingCanceled
events to the grid. This functionality is further explained in Section 4.4.3, 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 creates a simple editor that extends theJCStringCellEditor
class. TheMoneyCellEditor
class formats the data as money (two digits to the right of the decimal point) instead of a raw String; butJCStringCellEditor
does most of the work.The
import java.awt.Dimension;initialize()
method inMoneyCellEditor
takes the object passed in and creates a Money value for it. ThegetCellEditorValue()
method will pass the Money value back to the container.
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 code fragment is from an editor that was written without subclassing an existing editor. By implementing theJCCellEditor
interface, we have written an editor that will edit triangles. The editor handles bothInteger
andPolygon
data types. It initializes the editor with the object to be edited, either aNumber
or aPolygon
:
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, andgetCellEditorValue()
which returns the new Polygon created.
public boolean isModified() {
return new_poly != null;
}
public Object getCellEditorValue() {
return new_poly;
}
The
....JCCellEditor
interface defines thestopCellEditing()
method, which stops and commits the editing operation. In the case of this example, there isn't any validation taking place, so thestopCellEditing()
method will be unconditionally obeyed. TheTriangleCellEditor
also defines acancelCellEditing()
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. TheJCCellEditorSupport
class is a useful convenience class for editors that want to send events to JClass HiGrid programs.The
TriangleCellEditor
is an example of a fairly complex implementation of theJCCellEditor
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 ownJCCellEditor
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 theJCCellEditorSupport
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:For example, consider the
public void mouseReleased(MouseEvent e) {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 aneditingStopped
event when the mouse button is released:
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: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 HiGrid, for example). To do this, you need to use the
JCKeyModifier(int key, int modifier, boolean canInitializeEdit);JCKeyModifier
class to reserve a key/modifier combination: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 standardKeyEvent
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
andJCCellEditor
use theJCCellInfo
interface to get information about the cell. TheJCCellInfo
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 cells of the grid, including:
- foreground color
- background color
- selected foreground color
- selected background color
- font
- font metrics
- horizontal and vertical alignment
This information is fairly generic. The
com.klg.jclass.higrid
package also contains an object calledCellFormat
, which extendsCellStyle
and implementsJCCellInfo
to include more detailed information from the grid.
Figure 42 : The relationship of border sides, margins, and drawing
area provided by JCCellInfo.
The following code comprises the
import java.awt.Color;com.klg.jclass.cell.JCCellInfo
interface:
import java.awt.Rectangle;
import java.awt.Font;
import java.awt.Insets;
import javax.swing.SwingConstants;
public interface JCCellInfo {
public Color getBackground();
public Color getForeground();
public Color getSelectedBackground();
public Color getSelectedForeground();
public Font getFont();
public int getHorizontalAlignment();
public int getVerticalAlignment();
public Insets getMarginInsets();
public Insets getBorderInsets();
public int getBorderStyle();
public Rectangle getDrawingArea();
public boolean isEditable();
public boolean isEnabled();
public boolean getSelectAll();
public int getClipHints();
public Class getDataType();
![]() ![]() ![]() |