This tutorial shows how to create renderers for IJC. A renderer controls the formatting of a field type used to display data. For example, a renderer can set the alignment and font of integers, which can be different to the alignment and font of floats, because renderers are specified per field type.
The following sections are covered in this tutorial:
Contents:
- Prerequisite Knowledge
- Exploring the Sample
- Using the IDE's Wizards
- Coding the Module
- Conclusion
This tutorial assumes that you have worked through Getting Started with Extending IJC and that you are familiar with the concepts and instructions described therein. As before, you will use NetBeans IDE to create the sample that will extend IJC. As a result, you are assumed to be in possession of the required software and to know how to use NetBeans IDE to:
No time will be spent on the above topics in this tutorial.
Before we begin, let's look at the sample that we will build in this tutorial.
The Table Widget Settings dialog opens.
Here you see different renderers. One of them is called "Coloring Renderer (IJC API Example)". This is the renderer that we will create in this tutorial.
You now see the following:
The related column now has green/red cells, depending on whether the value is above or below the split value in the previous dialog:
Now that you are familiar with the feature that we will create in this tutorial, let's begin to set up our module source structure.
In this section, we use the New Module wizard, described in Getting Started with Extending IJC, to create a source structure.
You now have a source structure for your module.
Our next step is to specify which APIs we want to use. Each API is provided by a module. Some of the APIs belong to the NetBeans API, while others belong to the Instant JCHem API. Because our module is part of the IJC application, which itself makes use of all the NetBeans APIs and IJC APIs that we need, we can set dependencies on the modules that are available to the application.
| Module | Purpose | Classes Relevant to Our Module |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You should now see the following:
First, let's begin by creating an implementation of RendererFactory. This interface creates a renderer, depending on the level of suitability of the renderer for the field. Suitability is determined by the RendererFactory.getSuitabilityFor() method. The suitability level is an int between 0 (not supported) and 100 (best renderer for the given field). Below, if the field is an integer or a float, the suitability of the renderer is set to 30. As a result, it will most likely not be the default renderer, because other renderers are likely to have higher suitability levels.
Of the three types of widgets available, we will make our renderer applicable to table cells and IJC widget texts.
public class ColoringRendererFactory implements RendererFactory {
public int getSuitabilityFor(DFField field, RendererFactory.RendererType type) {
if (type.equals(RendererFactory.RendererType.TableCell) || type.equals(RendererFactory.RendererType.WidgetText)) {
DFFieldDataTypeCapability cap = (DFFieldDataTypeCapability) DIFUtilities.findCapability(field, DFFieldDataTypeCapability.class);
if ((cap instanceof DFFieldIntegerCapability) || (cap instanceof DFFieldFloatCapability)) {
return 30;
}
}
return UNSUPPORTED;
}
public TableCellRenderer createTableCellRenderer() {
return (TableCellRenderer) new ColoringRendererFactory();
}
public IJCWidgetRenderer createWidgetRenderer() {
throw new UnsupportedOperationException("Not supported yet.");
}
public IJCWidgetTextRenderer createWidgetTextRenderer() {
return (IJCWidgetTextRenderer) new ColoringRendererFactory();
}
}
Add the following to the layer.xml file. These entries create a new folder called "IJC", with a subfolder called "RendererFactories". Within that folder, we register our implementation of the RendererFactory interface.
<folder name="IJC">
<folder name="RendererFactories">
<file name="ColoringRendererFactory.instance">
<attr name="instanceClass" stringvalue="com.im.ijc.examples.ijcrenderers.ColoringRendererFactory"/>
<attr name="position" intvalue="100"/>
</file>
</folder>
</folder>
Let's create the renderer.
The IDE prompts you to let it create import statements and skeleton abstract methods. Let it do so and notice that the IDE creates the following skeleton class:
public class ColoringRenderer extends DefaultTableCellRenderer implements IJCWidgetTextRenderer, IJCWidgetTextRenderer.TextFormatInfo {
public ColoringRenderer() {
}
public TextFormatInfo getTextFormatInfo(Object value) {
throw new UnsupportedOperationException("Not supported yet.");
}
public String getValueAsText() {
throw new UnsupportedOperationException("Not supported yet.");
}
public boolean getLineWrap() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
public TextFormatInfo getTextFormatInfo(Object value) {
setValue(value);
return this;
}
public String getValueAsText() {
return getText();
}
public boolean getLineWrap() {
return false;
}
public static final String PROP_SPLIT_VALUE = "splitValue";
public static final String PROP_LOWER_COLOR = "lowerColor";
public static final String PROP_HIGHER_COLOR = "higherColor";
private float splitValue;
private Color lowerColor;
private Color higherColor;
public void setSplitValue(float splitValue) {
float oldValue = this.splitValue;
this.splitValue = splitValue;
firePropertyChange(PROP_SPLIT_VALUE, oldValue, splitValue);
}
public float getSplitValue() {
return splitValue;
}
public void setLowerColor(Color lowerColor) {
if (lowerColor == null) {
throw new IllegalArgumentException();
}
Color oldValue = this.lowerColor;
this.lowerColor = lowerColor;
firePropertyChange(PROP_LOWER_COLOR, oldValue, lowerColor);
}
public Color getLowerColor() {
return lowerColor;
}
public void setHigherColor(Color higherColor) {
if (higherColor == null) {
throw new IllegalArgumentException();
}
Color oldValue = this.higherColor;
this.higherColor = higherColor;
firePropertyChange(PROP_HIGHER_COLOR, oldValue, higherColor);
}
public Color getHigherColor() {
return higherColor;
}
public ColoringRenderer() {
splitValue = 0f;
lowerColor = new Color(200, 255, 200);
higherColor = new Color(255, 200, 200);
}
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setValue(value);
return this;
}
public void setValue(Object value) {
super.setValue(value);
if (value instanceof Number) {
Number n = (Number) value;
boolean under = (n.floatValue() < splitValue);
setBackground(under ? lowerColor : higherColor);
}
}
public class ColoringRenderer extends DefaultTableCellRenderer implements IJCWidgetTextRenderer, IJCWidgetTextRenderer.TextFormatInfo {
public static final String PROP_SPLIT_VALUE = "splitValue";
public static final String PROP_LOWER_COLOR = "lowerColor";
public static final String PROP_HIGHER_COLOR = "higherColor";
private float splitValue;
private Color lowerColor;
private Color higherColor;
public ColoringRenderer() {
splitValue = 0f;
lowerColor = new Color(200, 255, 200);
higherColor = new Color(255, 200, 200);
}
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setValue(value);
return this;
}
public void setValue(Object value) {
super.setValue(value);
if (value instanceof Number) {
Number n = (Number) value;
boolean under = (n.floatValue() < splitValue);
setBackground(under ? lowerColor : higherColor);
}
}
public void setSplitValue(float splitValue) {
float oldValue = this.splitValue;
this.splitValue = splitValue;
firePropertyChange(PROP_SPLIT_VALUE, oldValue, splitValue);
}
public float getSplitValue() {
return splitValue;
}
public void setLowerColor(Color lowerColor) {
if (lowerColor == null) {
throw new IllegalArgumentException();
}
Color oldValue = this.lowerColor;
this.lowerColor = lowerColor;
firePropertyChange(PROP_LOWER_COLOR, oldValue, lowerColor);
}
public Color getLowerColor() {
return lowerColor;
}
public void setHigherColor(Color higherColor) {
if (higherColor == null) {
throw new IllegalArgumentException();
}
Color oldValue = this.higherColor;
this.higherColor = higherColor;
firePropertyChange(PROP_HIGHER_COLOR, oldValue, higherColor);
}
public Color getHigherColor() {
return higherColor;
}
public TextFormatInfo getTextFormatInfo(Object value) {
setValue(value);
return this;
}
public String getValueAsText() {
return getText();
}
}
Let's create the bean info class.
public class ColoringRendererBeanInfo extends SimpleBeanInfo {
private static final int PROPERTY_splitValue = 0;
private static final int PROPERTY_lowerColor = 1;
private static final int PROPERTY_higherColor = 2;
public ColoringRendererBeanInfo() {
}
private static BeanDescriptor getBdescriptor() {
BeanDescriptor beanDescriptor = new BeanDescriptor(ColoringRenderer.class, null);
beanDescriptor.setDisplayName(NbBundle.getMessage(ColoringRendererBeanInfo.class, "TIT_ColoringRenderer"));
return beanDescriptor;
}
private static PropertyDescriptor[] getPdescriptor(){
PropertyDescriptor[] properties = new PropertyDescriptor[3];
try {
properties[PROPERTY_splitValue] = new PropertyDescriptor ( "splitValue", ColoringRenderer.class, "getSplitValue", "setSplitValue" ); // NOI18N
properties[PROPERTY_splitValue].setDisplayName ( NbBundle.getMessage(ColoringRendererBeanInfo.class, "NAME_SplitValue") );
properties[PROPERTY_lowerColor] = new PropertyDescriptor ( "lowerColor", ColoringRenderer.class, "getLowerColor", "setLowerColor" ); // NOI18N
properties[PROPERTY_lowerColor].setDisplayName ( NbBundle.getMessage(ColoringRendererBeanInfo.class,"NAME_LowerColor") );
properties[PROPERTY_higherColor] = new PropertyDescriptor ( "higherColor", ColoringRenderer.class, "getHigherColor", "setHigherColor" ); // NOI18N
properties[PROPERTY_higherColor].setDisplayName ( NbBundle.getMessage(ColoringRendererBeanInfo.class,"NAME_HigherColor") );
}
catch(IntrospectionException e) {
e.printStackTrace();
}
return properties;
}
private static EventSetDescriptor[] getEdescriptor(){
EventSetDescriptor[] eventSets = new EventSetDescriptor[0];
return eventSets;
}
private static MethodDescriptor[] getMdescriptor(){
MethodDescriptor[] methods = new MethodDescriptor[0];
return methods;
}
private static final int defaultPropertyIndex = -1;
private static final int defaultEventIndex = -1;
public BeanDescriptor getBeanDescriptor() {
return getBdescriptor();
}
public PropertyDescriptor[] getPropertyDescriptors() {
return getPdescriptor();
}
public EventSetDescriptor[] getEventSetDescriptors() {
return getEdescriptor();
}
public MethodDescriptor[] getMethodDescriptors() {
return getMdescriptor();
}
public int getDefaultPropertyIndex() {
return defaultPropertyIndex;
}
public int getDefaultEventIndex() {
return defaultEventIndex;
}
}
TIT_ColoringRenderer=Coloring Renderer (IJC API Example) NAME_SplitValue=The split value NAME_LowerColor=Color under the value NAME_HigherColor=Color above the value
Add the highlighted entries below to the IJC folder in the layer.xml:
<folder name="IJC">
<folder name="RendererFactories">
<file name="ColoringRendererFactory.instance">
<attr name="instanceClass" stringvalue="com.im.ijc.examples.ijcrenderers.ColoringRendererFactory"/>
<attr name="position" intvalue="100"/>
</file>
</folder>
<folder name="persistence">
<folder name="classes">
<file name="com_im_ijc_examples_ijcrenderers_ColoringRenderer.instance">
<attr name="implClass" stringvalue="com.im.ijc.renderers.xml.CellRendererPersister"/>
<attr name="instanceCreate" methodvalue="com.im.ijc.core.xml.beans.FSPersisterFactory.create"/>
<attr name="class" stringvalue="com.im.ijc.examples.ijcrenderers.ColoringRenderer"/>
<attr name="localname" stringvalue="coloringRenderer"/>
<attr name="namespace" stringvalue="http://informaticsmatters.com/xml/ijc/1.0"/>
<attr name="position" intvalue="1500"/>
</file>
</folder>
<folder name="xmlns">
<folder name="http___informaticsmatters_com_xml_ijc_1_0">
<file name="coloringRenderer.instance">
<attr name="implClass" stringvalue="com.im.ijc.renderers.xml.CellRendererPersister"/>
<attr name="instanceCreate" methodvalue="com.im.ijc.core.xml.beans.FSPersisterFactory.create"/>
<attr name="class" stringvalue="com.im.ijc.examples.ijcrenderers.ColoringRenderer"/>
<attr name="localname" stringvalue="coloringRenderer"/>
<attr name="namespace" stringvalue="http://informaticsmatters.com/xml/ijc/1.0"/>
<attr name="position" intvalue="600"/>
</file>
</folder>
</folder>
</folder>
</folder>
The new persistence folder that you need to add, as shown above, contains registration entries for writer-factories and reader-factories that store or read UI object settings. When a JavaBean is to be written, IJC looks up a writer (persister), using the class name (obj.getClass().getName()) to write the state to XML format. If such a persister is not registered, the persister for the superclass is tried, etc. A Beaninfo-based Persister is registered for j.o.Object, so each Object is ultimately written, but the object's class name is stored in the XML file, and all such objects create the element "ijc:simpleBean" in the XML.
Therefore, if possible, you should register the persister for a class, in order to get a nice XML element with a tag composed of "namespace":"localName", which looks a better. But, mainly, the reason you need to do this is to give Java Class names (which are implementation details that may change from release to release) "abstract" XML element names. You are free to change the implementation classes, provided that a Persister is registered to the element xmlns:localName in a future IJC release and assuming you will still be able to read old data.
There is boilerplate support for persisting (reading/writing) Beans implemented in the FSPersisterFactory class, which gets the BeanInfo for the persisted object and stores properties described by the BeanInfo, using BeanInfo-published get and set methods. Primitive values are stored as attributes, Object values are saved (read) using Persister for the type declared in the BeanInfo (or the actual type). Therefore, for simple objects, the simple declaration shown above is fine. For this basic support, these attributes are necessary:
For writing Beans, the Persister must be registered as follows:
/persistence/classes/class-name-with-dots-replaced-by-underscores
For reading Beans, the Persister must be registered as follows:
/persistence/xmlns/namespace-with-nonalphanum-replaced-by-underscores/localname-with-replaced-nonalphanum
You are recommended to register these using .instance DataObjects (using the .instance extension and "instanceCreate"/"class" attributes), since this is what is directly supported by the NetBeans core.
This Coloring Renderer example has a BeanInfo class that declares three properties. One of them is a 'float' type (a primitive). There is also a common Persister for the java.awt.Color class available, which encodes the Color object as an attribute that contains a hexadecimal color value. In other words, the FSPersisterFactory, as declared above, will follow the BeanInfo, serialize (or read) the "splitValue" as an attribute, and the "lowColor" and "highColor" using the Color persister. In effect, the Color Renderer is serialized as something like this:
<renderer splitValue="1.0" higherColor="#ffffff" lowerColor="#00000"/>
Above, the namespaces and implementation detail attributes are omitted for clarity.
Congratulations, you have created your first renderer for IJC. You simply need to run the module, as described earlier, and then IJC is extended with your new feature and you can use it and distribute it to your users.