Search code examples
javaswingjtabletablecellrenderer

Add behavior to a default JTable cell renderer


If I use the following to display a JTable:

package jtable.fontsize;
import java.awt.BorderLayout;
import java.awt.Font;
import java.lang.reflect.InvocationTargetException;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

import rcutil.swing.table.LastColumnChangesWidthJTable;


public class JTableCellPlay extends JFrame
{
  public JTableCellPlay()
  {
    super("setting Table font size");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }

  public static void main(String ... arguments) throws InvocationTargetException, InterruptedException
  {
    JTableCellPlay mainScreen = new JTableCellPlay();
    mainScreen.go();
  }

  public void go() throws InvocationTargetException, InterruptedException
  {
    SwingUtilities.invokeAndWait(new Runnable() { public void run() { createScreen(); } } );
    setVisible(true);
  }

  public void createScreen()
  {
    LastColumnChangesWidthJTable aLittleJTable = new LastColumnChangesWidthJTable(new LittleTableModel());
    aLittleJTable.setDefaultRenderer(Integer.class, new IntegerCellRenderer());
    JScrollPane scrollPane = new JScrollPane(aLittleJTable);
    add(scrollPane, BorderLayout.CENTER);
    pack();
  }
}

with the following renderer:

package jtable.fontsize;

import java.awt.Color;
import java.awt.Component;

import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;


public class IntegerCellRenderer extends JLabel implements TableCellRenderer
{
  @Override
  public Component getTableCellRendererComponent
                      (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
  {
    String resultString = String.format("~%d~", (Integer)value);
    setText(resultString);
    setHorizontalAlignment(JLabel.CENTER);
//    if (isSelected) 
//    { 
//      setOpaque(true);  // evidently necessary for JLabel as a component in a JTable cell
//      setBackground(Color.GRAY);
//    }
    return this;
  }
}

and the following model (so that your code will run, yes I know there are other ways to do this part):

package jtable.fontsize;

import javax.swing.table.AbstractTableModel;

public class LittleTableModel extends AbstractTableModel
{
  public Class<? extends Object> getColumnClass(int column)
  {
    Class<? extends Object> c = null;
    switch (column)
    {
    case 0: c = String.class; break;
    case 1: c = Integer.class; break;
    case 2: c = String.class; break;
    }
    return c;
  }

  String[] columnNames = { "first", "second", "third" };
  Object[][] data = { { "one", 2, "three" }, { "four", 5, "six" } };

  public int getColumnCount() { return 3; }
  public int getRowCount() { return 2; }
  public String getColumnName(int index) { return columnNames[index]; }

  public Object getValueAt(int row, int column)
  {
    Object result = data[row][column];
    return result;
  }
}

Then when I select one of the rows, the two String columns get a selection highlight but the "Integer" column does not.

If I comment out the line that sets the "Integer" renderer, then clicking on a row selects the entire row as expected.

I know I can use the commented-out lines in the renderer to highlight the cell, adding setOpaque(true) and setBackground(Color) and even play games to get the selected and non-selected background colors correct, but I suspect there is some way to use the same renderer that was used originally for things like that, and just use the code in my renderer to do the special stuff I need to do. Can someone explain how that works?


Solution

  • Why not simply give your renderer an else block where you undo the changes made in t he if block?

    if (isSelected) {
        setOpaque(true); 
        setBackground(Color.GRAY);
    } else {
        setOpaque(false); // allow underlying color to show
        setBackground(null); // no color added
    }
    

    Or another option is to have your renderer class extend DefaultTableCellRenderer rather than implement TableCellRenderer, allowing you to use the innate highlighting abilities of the default renderer:

    @SuppressWarnings("serial")
    class IntegerCellRenderer2 extends DefaultTableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
                int row, int column) {
            String resultString = String.format("~%d~", (Integer) value);
            setText(resultString);
            setHorizontalAlignment(JLabel.CENTER);
            return super.getTableCellRendererComponent(table, resultString, isSelected, hasFocus, row, column);
        }
    }
    

    Note that you can set some properties of the renderer within its constructor. For example:

    @SuppressWarnings("serial")
    class IntegerCellRenderer2 extends DefaultTableCellRenderer {
        public IntegerCellRenderer2() {
            setHorizontalAlignment(JLabel.CENTER);
        }
    
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
                int row, int column) {
            String resultString = String.format("~%d~", (Integer) value);
            setText(resultString);
            // setHorizontalAlignment(JLabel.CENTER);
            return super.getTableCellRendererComponent(table, resultString, isSelected, hasFocus, row, column);
        }
    }