I'm writing an application for processing scientific data. The data is displayed in a jTable. I'm done with the data processing itself, now I want to do some conditional formatting to make interpretation easier for the end user.

  1. The last column of the table contains a threshold value, and if the data in a particular cell is below the threshold at the end of the row the fond needs to be changed to a different color.
  2. On top of that, if a value is below zero the background color of that cell needs to be changed. This rule applies to the entire table.

So basically for the first problem the conditional formatting has to be set in a different way for every row.
I know it will involve overriding the getTableCellRendererComponent, but from the examples I've studied I can't quite figure out how to this on a row per row basis, I'm totally new to jTable.

Any suggestions/advice/good examples to get me going?

There is not a row renderer per se, so you have to install a cell renderer on all columns that will check the particular column values and apply the formatting as necessary.

Here is a skeletal highlight renderer to give you an idea to work from

class HighlightRenderer extends DefaultTableCellRenderer {

    private final Color HIGHLIGHT_COLOR = Color.YELLOW;

    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);

        if ( [highlight condition] ) {
            if (isSelected) {
                comp.setBackground(new Color((HIGHLIGHT_COLOR.getRGB() 
                        ^ comp.getBackground().getRGB())));
                comp.setForeground(new Color(Color.BLACK.getRGB() 
                        ^ comp.getForeground().getRGB()));
            } else {
                comp.setBackground(HIGHLIGHT_COLOR);
                comp.setForeground(Color.BLACK);
            }
        } else {
            if (isSelected) {
            } else {
                comp.setBackground(Color.WHITE);
                comp.setForeground(Color.BLACK);
            }
        }
        return comp;
    }
}

You can install the renderer directly on the columns

TableColumnModel colModel = table.getColumnModel();
            for (Enumeration<TableColumn> colEnum = colModel.getColumns(); colEnum.hasMoreElements();) {
                TableColumn c = colEnum.nextElement();
                c.setCellRenderer(new HighlightRenderer());
            }

or override getCellRenderer() on the JTable as noted in the JTable tutorial here.

Edited 5 Years Ago by Ezzaral: n/a

Comments
that job for prepareRenderer, don't do it that this way

I don't get it, you think its possible to work from the code samples you provided without overriding getCellRenderer()?
If you look at line 11 for instance, in my case if I have a table with 20 rows I'd have 20 different highlight conditions. And I can only set the renderer per column?

Edited 5 Years Ago by cozmo87: n/a

No, the note at the bottom about overriding getCellRenderer() is about how you set the renderer on the column. You can override that method to return an instance of your custom TableCellRenderer or you can set it explicitly through the column model with the TableColumn.setCellRenderer() method. Those two methods only deal with how you specify the renderer component to use.

Don't confuse that with the getTableCellRendererComponent() method of the TableCellRenderer interface, which is what you override to specify how the cell is rendered.

> in my case if I have a table with 20 rows I'd have 20 different highlight conditions.

It's really just two highlighting conditions that you mentioned:
- table.getValueAt(row, whateverColumnToCheck) < threshold => change font on component
- value < 0 => change color on component

The parameters to getTableCellRendererComponent() pass you all that info you need to check those conditions.

Edited 5 Years Ago by Ezzaral: n/a

Took a bit of a brake on the project, but I'm looking further into it now. My current method somewhat works but seems still broken.I'm trying to set the font of the values to red if the value is bellow the threshold which is in the last column of the same row.
What the current program does if (cellValue < threshold) is true, it does indeed set the the font of that cell to red, but all the following values of the same column are also set to red. Moreover if I scroll around all the values that have gone out of sight will also become red.
There are no compilation errors.

Also, I have to hardcode the index of the last column:
table.getWidth() returns 1199 while it should be 15.

static class tableRenderer extends DefaultTableCellRenderer {

    @Override
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            JLabel renderedLabel = (JLabel) super.getTableCellRendererComponent(
            table, value, isSelected, hasFocus, row, column);
            renderedLabel.setHorizontalAlignment(SwingConstants.RIGHT);


            int lastColumnIndex2 = table.getWidth() -1;
            System.out.println("Index =" + lastColumnIndex2);
            int lastColumnIndex = 15;
            double threshold = 0;
            double cellValue = 0;

            if (column >= 3){

                cellValue = (Double)table.getValueAt(row, column);
                threshold = (Double)table.getValueAt(row, lastColumnIndex);
                if (cellValue < threshold){
                    renderedLabel.setForeground(new Color(181, 78, 93));
                }
            }
            

            return renderedLabel;
        }
}
TableColumnModel colModel = jTable2.getColumnModel();
        for (Enumeration<TableColumn> colEnum = colModel.getColumns(); colEnum.hasMoreElements();) {
            TableColumn c = colEnum.nextElement();
            c.setCellRenderer(new tableRenderer());
        }

don't do it this way, that job for prepareRenderer, the best examples for Swing, btw prepareRenderer covered with its funcionalities

getTableCellRendererComponent

too, don't declare JLabel (if there isn't TableCellEditor), all Renderers in Swing by defalut returns JLabel as J/Component just declare

setHorizontalAlignment(SwingConstants.RIGHT);
setForeground(new Color(181, 78, 93));
.....

really working example for prepareRenderer is here

Edited 5 Years Ago by mKorbel: n/a

Comments
Nice info. Thanks for posting this.

@mKorbel: Interesting link. Thanks for posting that, as I hadn't run across that way of doing it before.

Thanks Ezzaral, it works now. Also for the width of the table I should have called getColumnCount() instead of getWidth()

What might be the advantage of using prepareRenderer?
Anyways I'm using Netbeans and it doesn't allow me to modify the declaration of my variables.(or I don't know how to)

The working code:

static class tableRenderer extends DefaultTableCellRenderer {

    @Override
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

            JLabel renderedLabel = (JLabel) super.getTableCellRendererComponent(
            table, value, isSelected, hasFocus, row, column);
            int lastColumnIndex = table.getColumnCount() -1;
            double threshold = 0;
            double cellValue = 0;

            if (column > 3){ //first 3 columns always contain strings

                cellValue = (Double)table.getValueAt(row, column);
                threshold = (Double)table.getValueAt(row, lastColumnIndex);
                if (cellValue < threshold){
                    renderedLabel.setForeground(new Color(181, 78, 93));
                }else if (cellValue >= threshold){
                    renderedLabel.setForeground(new Color(0, 0, 0));
                }
            }           
            return renderedLabel;
        }
}

> Anyways I'm using Netbeans and it doesn't allow me to modify the declaration of my variables.(or I don't know how to)

You can customize the create/initialization code for components added through the designer by selecting the component and then selecting the "Code" tab in the Properties window. To override a method, you would add your code to the "Custom Creation Code" property.

Edited 5 Years Ago by Ezzaral: n/a

Ok guys one more question:
My first column contains booleans and now that I applied my custom tablecellrenderer those booleans are shown as true and false instead of check boxes. What would be a clean way to restore just the first column to the default renderer?

Just don't install your custom renderer for that column. Since you're iterating all of the columns in the model and adding it, just skip the first one.

@cozmo87 ColumnRenderer you have activate after all changes in JTable, otherwise doesn't work, you ignored all my suggestion, instead 10-25codes lines (that's work in all cases excepts remove, reordering, add new TableColumn) you probably to going to go ... very long way that required lots of skills about Swing, JTable, Concurency, KeyEvents and Event's SubSystem,

after all changes (Rows/Column - add/remove/reordering, TableCell- change/input/delete TableCell contents) you have to call your renderer immediatelly, otherwise nothing should be changed in JTable

much luck

Edited 5 Years Ago by mKorbel: n/a

after all changes (Rows/Column - add/remove/reordering, TableCell- change/input/delete TableCell contents) you have to call your renderer immediatelly, otherwise nothing should be changed in JTable

much luck

That's just a matter of calling one of the fireTable* methods:

public class MyTableModel extends AbstractTableModel {
  ...
  public void addRow(MyRowData rowData) {
    // do my custom code here
    fireTableDataChanged();
  }
  ...
}

Also mKorbel, I got rid of the jLabel as you suggested. Selected cells are now rendered correctly with a blue background and a white fond. When I declared jLabel the fond in the selected cells would stay black/red.

static class tableRenderer extends DefaultTableCellRenderer {
    
    @Override
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

            int lastColumnIndex = table.getColumnCount() -1;
            double threshold = 0;
            double cellValue = 0;

            if (column > 3){ //first 3 columns always contain strings

                cellValue = (Double)table.getValueAt(row, column);
                threshold = (Double)table.getValueAt(row, lastColumnIndex);
                if (cellValue < threshold){
                    setForeground(new Color(181, 78, 93));
                }else if (cellValue >= threshold){
                    setForeground(new Color(0, 0, 0));
                }
            }

            return super.getTableCellRendererComponent(
            table, value, isSelected, hasFocus, row, column );
        }
}

@cozmo87

there is your problem

return this;

instead of

return super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column );

here is somethig that never ever reinvent the wheel

import java.awt.*;
import java.awt.event.*;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.table.*;

public class EditorTest {

    private JScrollPane getTableComponent() {
        String[] colNames = {"Stock", "Price", "Shares", "Quantity", "Action", "Action", "Holder"
        };
        final Object[][] data = {
            {"MSFT", Double.valueOf(12.21), Integer.valueOf(10), Integer.valueOf(0), "Buy", "Sell", "Bill"},
            {"IBM", Double.valueOf(13.21), Integer.valueOf(12), Integer.valueOf(0), "Buy", "Sell", "Tim"},
            {"ORACLE", Double.valueOf(21.22), Integer.valueOf(11), Integer.valueOf(0), "Buy", "Sell", "Tom"}
        };
        DefaultTableModel model = new DefaultTableModel(data, colNames) {

            private static final long serialVersionUID = 1L;

            @Override
            public Class getColumnClass(int col) {
                return data[0][col].getClass();
            }
        };
        JTable table = new JTable(model);
        TableColumnModel colModel = table.getColumnModel();
        colModel.getColumn(1).setCellRenderer(new DoubleRenderer());
        colModel.getColumn(3).setCellRenderer(new SpinnerRenderer());
        colModel.getColumn(4).setCellRenderer(new ButtonRenderer());
        colModel.getColumn(5).setCellRenderer(new ButtonRenderer());
        colModel.getColumn(3).setCellEditor(new SpinnerEditor());
        colModel.getColumn(4).setCellEditor(new ButtonEditorA(table));
        colModel.getColumn(5).setCellEditor(new ButtonEditorA(table));
        table.setCellSelectionEnabled(true);
        Dimension d = table.getPreferredSize();
        table.setPreferredScrollableViewportSize(d);
        return new JScrollPane(table);
    }

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(new EditorTest().getTableComponent());
        f.pack();
        f.setLocation(100, 100);
        f.setVisible(true);
    }
}

class SpinnerEditor extends AbstractCellEditor implements TableCellEditor {

    private static final long serialVersionUID = 1L;
    private JTable table;
    private SpinnerNumberModel model = new SpinnerNumberModel(0, 0, null, 1);
    private JSpinner spinner = new JSpinner(model);
    private int clickCountToStart = 1;

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        spinner.setValue(((Integer) value).intValue());
        return spinner;
    }

    @Override
    public Object getCellEditorValue() {
        return (Integer) spinner.getValue();
    }

    @Override
    public boolean isCellEditable(EventObject anEvent) {
        if (anEvent instanceof MouseEvent) {
            return ((MouseEvent) anEvent).getClickCount() >= clickCountToStart;
        }
        return true;
    }

    @Override
    public boolean shouldSelectCell(EventObject anEvent) {
        return true;
    }

    @Override
    public boolean stopCellEditing() {
        return super.stopCellEditing();
    }

    @Override
    public void cancelCellEditing() {
        super.cancelCellEditing();
    }
}

class ButtonEditorA extends AbstractCellEditor implements TableCellEditor, ActionListener {

    private static final long serialVersionUID = 1L;
    private JTable table;
    private JButton button = new JButton();
    private NumberFormat nf = NumberFormat.getCurrencyInstance();
    private int clickCountToStart = 1;

    public ButtonEditorA(JTable table) {
        this.table = table;
        button.addActionListener(this);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        nf.setMaximumIntegerDigits(2);
        nf.setMinimumIntegerDigits(2);
        nf.setRoundingMode(RoundingMode.HALF_EVEN);
        StringBuilder sb = new StringBuilder();
        int row = table.getEditingRow();
        int col = table.getEditingColumn();
        //System.out.printf("row = %d  col = %d%n", row, col);
        sb.append((String) table.getValueAt(row, 6));
        sb.append(" has ");
        sb.append(((col == 4) ? "bought " : "sold "));
        sb.append(((Integer) table.getValueAt(row, 3)).toString());
        sb.append(" shares of ").append((String) table.getValueAt(row, 0));
        sb.append(" at ").append(nf.format(((Double) table.getValueAt(row, 1)).doubleValue()));
        stopCellEditing();
        System.out.println(sb.toString());
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int row, int column) {
        button.setText(value.toString());
        return button;
    }

    @Override
    public Object getCellEditorValue() {
        return button.getText();
    }

    @Override
    public boolean isCellEditable(EventObject anEvent) {
        if (anEvent instanceof MouseEvent) {
            return ((MouseEvent) anEvent).getClickCount() >= clickCountToStart;
        }
        return true;
    }

    @Override
    public boolean shouldSelectCell(EventObject anEvent) {
        return true;
    }

    @Override
    public boolean stopCellEditing() {
        return super.stopCellEditing();
    }

    @Override
    public void cancelCellEditing() {
        super.cancelCellEditing();
    }
}

class SpinnerRenderer implements TableCellRenderer {

    private SpinnerNumberModel model = new SpinnerNumberModel(0, 0, null, 1);
    private JSpinner spinner = new JSpinner(model);

    @Override
    public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        spinner.setValue(((Integer) value).intValue());
        return spinner;
    }
}

class ButtonRendererA implements TableCellRenderer {

    private JButton button = new JButton();

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        button.setText(value.toString());
        return button;
    }
}

class DoubleRenderer extends DefaultTableCellRenderer {

    private static final long serialVersionUID = 1L;
    private NumberFormat nf = NumberFormat.getCurrencyInstance();

    public DoubleRenderer() {
        setHorizontalAlignment(RIGHT);
    }

    @Override
    public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        nf.setMaximumIntegerDigits(2);
        nf.setMinimumIntegerDigits(2);
        nf.setRoundingMode(RoundingMode.HALF_EVEN);
        setText(nf.format(((Double) value).doubleValue()));
        return this;
    }
}
This question has already been answered. Start a new discussion instead.