Company News

<< Previous PageNext Page >>

TableCellRenderer, Part 3 - Delegate Renderer Examples(13:56, 25. Aug. 2010)

As already mentioned in the last article modifying the cell background color of a JTable in a delegate renderer is a bit tricky. This article describes possible pitfalls and you will learn how to work around color caching.

Column Highlighting

Let's start with a very simple example - the intention is to highlight a single table column.

/** Example #1 **/ 
class SimpleColumnBackgroundRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;

  public SimpleColumnBackgroundRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                          hasFocus, row, column);      
    if (!isSelected)
      c.setBackground(new Color(0xFFF0E0));
    return c;
  }
} 

We apply the renderer to the sixth column.

TableCellRenderer defaultRenderer = table.getDefaultRenderer(Object.class);
TableCellRenderer r = new SimpleColumnBackgroundRenderer(defaultRenderer);
table.getColumnModel().getColumn(5).setCellRenderer(r);

Take a look at the result below.

""

Oops, if you wonder why three columns are highlighted, you are not alone. As already mentioned in the last article the problem is caused by DefaultTableCellRenderer which implements a caching mechanism for background/foreground colors and icons. Technically our renderer handles the sixth column only but the delegate is also responsible for all columns without a registered renderer. To fix this behavior we have to register our renderer for all related columns and have to reset the background color within our renderer just like below.

/** Example #2 **/ 
TableCellRenderer defaultRenderer = table.getDefaultRenderer(Object.class);
TableCellRenderer r = new ColumnBackgroundRenderer(defaultRenderer);
table.setDefaultRenderer(Object.class, r);
   
...
class ColumnBackgroundRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;
  private Color alternateColor; 

  public ColumnBackgroundRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
    alternateColor = UIManager.getColor("Table.alternateRowColor");
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                          hasFocus, row, column);      
    if (!isSelected)
    {  
      int modelColumn = table.convertColumnIndexToModel(column);
      //respect alternating row background when resetting
      Color defaultBackground = (row % 2 == 0 && alternateColor != null) ? alternateColor : table.getBackground();
      c.setBackground(modelColumn == 5 ? new Color(0xFFF0E0) : table.getBackground());
    }  
    return c;
  }
} 

One question still remains - what's the reason for the color caching implementation in DefaultTableCellRenderer? Performance - no, there's no difference. One can only assume that the caching was made to enable simple column coloring as below.

/** Example #3 **/ 
TableCellRenderer r = new DefaultTableCellRenderer();
((Component)r).setBackground(Color.YELLOW);
table.getColumnModel().getColumn(5).setCellRenderer(r);
table.getColumnModel().getColumn(3).setCellRenderer(r);

Anyway, generally this implementation detail makes things more complicated than necessary. That's why we've decided to disable color caching by default in the upcoming Synthetica release V2.11. To control the caching behavior the UI-property "Synthetica.table.cellRenderer.colorCache.enabled" will be introduced with V2.11.

Note: Because of the color caching workaround, Example#2 should work well for most available look and feels. For look and feels which install caching free-renderers (like Synthetica V2.11 and above) also Example#1 works fine. Avoid the renderer usage of Example#3 - it's very implementation specific.

Row Highlighting

In the next step we extend our renderers to highlight a the third table row. To achieve this, we have to register our renderer for each table column.

/** Example #4 **/
for (int i = 0; i < table.getColumnModel().getColumnCount(); i++)
{  
  Class columnClass = table.getModel().getColumnClass(i);
  TableCellRenderer defaultRenderer = table.getDefaultRenderer(columnClass);
  TableCellRenderer colRenderer = new ColumnRowBackgroundRenderer(defaultRenderer);
  table.getColumnModel().getColumn(i).setCellRenderer(colRenderer);
}

The related renderer takes care about row and column coloring and also respects alternating row background (if enabled by UI-property).

/** Example #5 **/
class ColumnRowBackgroundRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;
  private Color alternateColor; 

  public ColumnRowBackgroundRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
    alternateColor = UIManager.getColor("Table.alternateRowColor");
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                                hasFocus, row, column);      
    if (!isSelected)
    {  
      int modelColumn = table.convertColumnIndexToModel(column);
      //Because of the color caching we have to reset the background to table background.
      //If color caching is disabled you can also use c#getBackground instead of 
      //table#getBackground. For resetting also respect alternating row background.
      Color defaultBackground = (row % 2 == 0 && alternateColor != null) ? alternateColor : table.getBackground();
      c.setBackground(modelColumn == 5 ? new Color(0xFFF0E0) : defaultBackground);
      c.setBackground(row == 2 ? new Color(0xDDD7FF) : c.getBackground());
    }  
    return c;
  }
}

In Synthetica V2.11 (scheduled for September 2010) the same result can be achieved with the more simple renderer below.

/** Example #6 - requires Synthetica V2.11 or above **/
class SimpleColumnRowBackgroundRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;

  public SimpleColumnRowBackgroundRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                                hasFocus, row, column);      
    if (!isSelected)
    {  
      int modelColumn = table.convertColumnIndexToModel(column);
      if (modelColumn == 5)
        c.setBackground(new Color(0xFFF0E0));
      if (row == 2)
        c.setBackground(new Color(0xDDD7FF));
    }  
    return c;
  }
} 

Here's the result (with alternating row background enabled):

""

""

SwingX related note: Delegate renderers also work fine for JXTables. However, the JXTable component comes along with a sophisticated highlighting feature which is recommended to be used - check it out if you are not bound to JTable.

Download

Related Posts

The next article will provide some advanced examples... CU

TableCellRenderer, Part 2 - How To Create A Custom Renderer(10:06, 11. Aug. 2010)

Generally there are three basic approaches to create a custom renderer.

  • Implementing a TableCellRenderer from scratch
  • Extending javax.swing.table.DefaultTableCellRenderer
  • Create a renderer-chain by using the already installed renderer as delegate

Let's take a closer look at each solution to find out which one is the best for your needs.

Implementing a TableCellRenderer from scratch

For the first solution you have to implement each rendering detail by yourself - this is error prone and keeping your custom renderer independent from the look and feel can become complex and therefore time consuming. Additionally you have to take care about performance. This solution is only recommended if no other approach fits your needs.

Extending DefaultTableCellRenderer

For creating a custom renderer developers often tend to extend javax.swing.table.DefaultTableCellRenderer. The howto can be found at Oracle's JTable tutorial. Unfortunately this solution is not quite perfect because the active look and feel possibly already installed it's own renderers. Third party table components like JXTable also install specialized renderers. So in case that you install a renderer which is derived from DefaultTableCellRenderer you can loose some settings like focus border, padding, alignment, font or colors.

Create a renderer-chain by using the already installed renderer as delegate

Even if deriving from DefaultTableCellRenderer is good enough for your needs - the most robust and preferred solution is to use the original renderer as delegate for your custom renderer. In the basic example below the rendering component runs through a renderer-chain before the component will be painted by the CellRendererPane.

JTable myTable = new JTable(...);
final TableCellRenderer renderer = myTable.getDefaultRenderer(Object.class);
myTable.setDefaultRenderer(Object.class, new TableCellRenderer(){
  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = renderer.getTableCellRendererComponent(table, value, isSelected, 
                                                         hasFocus, row, column);
    //apply all needed settings to c
    ...
    return c;
  }
}); 

By putting your custom renderer in a separate class it will become reusable.

JTable myTable = new JTable(...);
TableCellRenderer renderer = myTable.getDefaultRenderer(Object.class);
myTable.setDefaultRenderer(Object.class, new CustomTableCellRenderer(renderer);

public class CustomTableCellRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;

  public CustomTableCellRenderer(TableCellRenderer defaultRenderer)
  {
    this.delegate = defaultRenderer;
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                                hasFocus, row, column);
    //apply all needed settings to c
    ...
    return c;
  }
} 

Normally a single component instance is used for rendering - this also means that you are responsible for resetting modified component properties just like in the example below.

public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = renderer.getTableCellRendererComponent(table, value, isSelected, 
                                                         hasFocus, row, column);
    if (!isSelected)
    { 
      //highlight row #10
      if (row == 9)
        c.setBackground(Color.YELLOW);
      //reset background
      else
        c.setBackground(table.getBackground());
    }
    return c;
  }
}); 

Note: In case that your custom renderer modifies the background or foreground color, please make sure to apply the renderer to the Object.class. This ensures that your renderer will be called for each table cell and your settings will be applied to the renderer component. Swing's DefaultTableCellRenderer implements a caching mechanism which can result in wrong cell colors if you ignore this rule. For compatibility reasons Synthetica's default renderers acts like the Swing renderers. However, in Synthetica V2.11.0 and above an optional UI-property can be set to disable color caching.

Related Posts

In the upcoming article we examine the delegate method including more complex examples in detail.

TableCellRenderer, Part 1 - The Basics(11:00, 30. Jul. 2010)

As you maybe know a TableCellRenderer is an interface with a single method which returns a component - the returned component is used for rendering table data.

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

Swing's default implementation is DefaultTableCellRenderer and is based on JLabel - simply because a label is good enough to display most kind of data. The default renderer returns itself as cell renderer component. For customization it's helpful to keep in mind that the default renderer component is a JLabel instance.

JTable provides a method which allows you to install different renderers for different classes. This means you can specify a renderer for i.e. String objects, Number objects and of course for your own custom data objects.

JTable#setDefaultRenderer(Class<?> columnClass, TableCellRenderer renderer)

When you specify a renderer for a class, the renderer will be only called if the registered class matches with the related column data class. Generally a table creates and registers a bunch of renderers under the hood so you won't get bothered with specifying renderers for simple tables. The most generic types of renderers take care of the classes Object and Boolean.

Additional renders for the classes Number, Float, Double, Icon, ImageIcon, and Date will be additionally created. These renderers are derived from DefaultTableCellRenderer. Note: A Synth based look and feel, by default, installs renderers for Object and Boolean classes only. In Synth the default renderer (for Object classes) also takes care about rendering the mentioned classes (Number, Float...) in a specific manner.

So technically the main difference by using a Synth based look and feel is that a maximum of two renderer instances (Object, Boolean) are responsible for cell rendering by default. Keep this fact in mind if you have to create a custom renderer. In Synthetica releases above 2.10.1 you can modify this behavior - by default Synthetica behaves as intended by Synth.

JTable also allows you to specify renderers for particular columns through a TableColumn. A table column can be accessed by JTable#getColumnModel().getColumn(columnIndex).

TableColumn#setCellRenderer(TableCellRenderer cellRenderer)

The next article explains how to create and use a custom renderer and what you have to consider.

SyntheticaAddons V1.2.0 released!(14:10, 17. Jun. 2010)

We are pleased to announce release V1.2.0 of SyntheticaAddons - the Java/Swing component suite for Synthetica. Please find all major improvements below - a complete list of changes can be found at the changelog.

  • New Component JYSearchField with prompt, search and clear button support

JYSearchField

  • New Component JYTextField with prompt support and the ability to add leading and trailing components within the text area

JYTextField

  • New Component JYCheckBox - an extended JCheckBox with half selected state support

JYCheckBox

  • New Component JYCheckBoxTree - a JTree based component which allows selection by checkbox including half selected state support

JYCheckBoxTree

  • New Component JYSwitchButton - a checkBox variant with a switchable On/Off text area

JYSwitchButton

  • JYDocking - added stream support for restore/store operations (see IPerspectiveManager)
  • JYDocking - new method which allows you to define your own docking rules (see IDockable, IDockableAcceptor)
  • DateComboBox - time selection support
  • DateComboBox - year selection support by spinner within popup
  • Updated DemoCenter application demonstrates new components and examples

Related Links

Synthetica V2.10.1 released!(11:52, 14. Jun. 2010)

The maintenance release of Synthetica (V2.10.1) is available for downloading. The update fixes a bug in conjunction with SyntheticaAddons components, improves content pane handling and updates the Simple2D theme.

For more informations see Changelog.

Window Decoration, Part 5 - TitlePane Variations(09:25, 03. Jun. 2010)

Synthetica V2.10 supports some new UI-properties which allows you to customize the title pane layout for your frames and dialogs. The image below demonstrates a regular title pane with the Simple2D theme.

RegularTitlePane

However, for a more modern look you maybe prefer a different layout with a larger icon (menu button) and a slightly different menu bar location. As you maybe know Synthetica provides style support for multiple component instances. So it's possible to use a different layout only for the main screen of your application - other windows are not affected and appear in regular style.

Large TitlePane

An additional synth file contains all necessary declarations of the named default properties. Please note that the window name (LargeTitlePane) is appended to each needed default property.

<synth>

  <style id="largeTitlePaneWindow">
    <defaultsProperty key="Synthetica.rootPane.titlePane.showMenuBarInTitlePane.LargeTitlePane" type="boolean" value="true" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.menuButton.useOriginalImageSize.LargeTitlePane" type="boolean" value="true" />    
    <defaultsProperty key="Synthetica.rootPane.titlePane.menuButton.alignment.LargeTitlePane" type="integer" value="11" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.menuButton.insets.LargeTitlePane" type="insets" value="2 0 0 0" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.iconifyButton.alignment.LargeTitlePane" type="integer" value="11" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.iconifyButton.insets.LargeTitlePane" type="insets" value="2 0 0 0" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.toggleButton.alignment.LargeTitlePane" type="integer" value="11" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.toggleButton.insets.LargeTitlePane" type="insets" value="2 0 0 0" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.closeButton.alignment.LargeTitlePane" type="integer" value="11" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.closeButton.insets.LargeTitlePane" type="insets" value="2 0 0 0" />
  </style>  

  <style id="slimTitlePaneWindow">
    <defaultsProperty key="Synthetica.rootPane.titlePane.showMenuBarInTitlePane.SlimTitlePane" type="boolean" value="true" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.title.visible.SlimTitlePane" type="boolean" value="false" />
    <defaultsProperty key="Synthetica.rootPane.titlePane.menuBar.insets.SlimTitlePane" type="insets" value="-20 0 0 0" />
  </style>  
    
</synth>

In your application you have to load the additional configuration file through a custom loader just like below.

UIManager.setLookAndFeel(new SyntheticaSimple2DLookAndFeel(){
    @Override
    protected void loadXMLConfig(String fileName) throws ParseException
    {
      super.loadXMLConfig(fileName);
      super.loadXMLConfig("/demo/titlepane/titlePaneVariations.xml");
    }
  });

After setting the window name to the configured value the new style appears.

JFrame f = new JFrame("Large TitlePane Window");
//apply new window style
f.setName("LargeTitlePane");

Sometimes, when you need as much space as possible for the content area of your application, it's useful to place the menubar on top - in this case the window title disappears. In the screenshot below the slim title pane style (SlimTitlePane) is set.

Slim TitlePane

WebStart Demo

Download Demo Sourcecode

Note: To keep executables as small as possible, the webstart app doesn't includes the Java2D-library SyntheticaBatik - therefore Java 6 is required for proper execution.

Related Posts

Synthetica V2.10 released!(11:10, 31. May. 2010)

We are pleased to announce the release of Synthetica V2.10.0. Please find all major improvements below - a complete list of changes can be found at the changelog.

  • Added support to display menu bar within the window title pane.
  • Complete maximized support for translucent windows.
  • Improved table paint performance - up to 30%.
  • Improved key binding support for MAC.
  • New option to improve pressed state appearance for JCheckBox and JRadioButtons.
  • Ability to embed your preferred font resource (TTF) into a theme.
  • Improved painting support for (selected) cell renderers (List, Table, Tree, ComboBox).
  • Improved background painting support for table header.
  • Improved tabbed pane painter to support more complex styles.
  • Improved drop shadow support for window titles.
  • Improved styling support for indeterminate progress bars by new UI-properties.
  • New UI-property to specify window title font size.
  • New UI-property to specify font size for toplevel menus.
  • New UI-property to specify default toolBar opacity.
  • New UI-properties to specify custom table sort icons.
  • New UI-property to specify a custom border for table header cells.
  • Updated Simple2D and BlackEye theme.
Product Links
<< Previous PageNext Page >>