javaswingjtableswingxjxtable

Very unusual cell highlighting in JXTable


I have a JXTable set up to paint the selected cell in a certain color. However, whenever the selected cell is in the top row, it appears as though ALL cells in the table are painted.

Can anyone help me understand why, and how to resolve this issue?

The smallest working example to demonstrate this issue is below.

Additional info: the DefaultTableCellRenderer is re-applied every time Paint() is called, because it is a placeholder for the CustomTableCellRenderer that I use in my full program. Interestingly, if I remove the DefaultTableCellRenderer line, my issue is resolved. Why is this? Surely, if a renderer is not specified, the Default is used anyway??

import java.awt.Color;
import java.awt.Component;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.HighlightPredicate;

public class MainClass {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        CustomTableModel tableModel = new CustomTableModel();
        JXXTable table = new JXXTable(tableModel);
        JScrollPane scrollPane = new JScrollPane(table);
        SelectionListener listener = new SelectionListener(table);
        table.getSelectionModel().addListSelectionListener(listener);    
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        frame.add(scrollPane);
        frame.setVisible(true);
        frame.pack();
    }
}

class JXXTable extends JXTable {
    public JXXTable(CustomTableModel model){ super(model); }
    public void Paint(){
        this.setDefaultRenderer(Object.class, new DefaultTableCellRenderer());
        this.setHighlighters();
        HighlightPredicate predicate = new HighlightPredicate() {
            public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
                return adapter.hasFocus();
            }
        };
        this.addHighlighter(new ColorHighlighter(predicate, null, null, new Color(115,164,209), Color.WHITE)); 
    }
}

class SelectionListener implements ListSelectionListener {
    JXXTable table;
    public SelectionListener(JXXTable table) { this.table = table; }
    public void valueChanged(ListSelectionEvent e) { table.Paint(); }
}

class CustomTableModel extends AbstractTableModel {
    ArrayList<Object[]> al;

    public CustomTableModel() {
        al = new ArrayList<Object[]>(); 
        Object[] row = {1,2,3,"A","Collection","of","Random","Strings",9,10}; al.add(row);
        Object[] row2 = {11,12,13,"Another","Collection","Of","Random","Strings",19,20}; al.add(row2);
        Object[] row3 = {11,12,13,"Another","Collection","Of","Random","Strings",19,20}; al.add(row3);
        Object[] row4 = {11,12,13,"It","just","gets","more","random...",19,20}; al.add(row4);
    }

    public int getRowCount() { return al.size(); }
    public int getColumnCount() { return 10; }
    public Object getValueAt(int rowIndex, int columnIndex) { return al.get(rowIndex)[columnIndex]; }
}

Solution

  • For others having this problem, this was my solution.

    The problem was that a new TableCellRenderer object was being created every time the Paint() method of the table was being called (ie. every time the selection changed). The solution was to create the renderer object as a field of the table object, and simply to re-apply it (rather than re-create it) in the Paint() method. This renderer also needed to be applied during the table's constructor.

    Another code improvement I made during this process was to apply the same rule above to the ColorHighlighter and HighlightPredicate objects.

    ie. the table class becomes:

    class JXXTable extends JXTable {
        DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
        HighlightPredicate predicate = new HighlightPredicate(){
            public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
                return adapter.hasFocus();
            }
        };
        Highlighter cellHighlight = new ColorHighlighter(predicate, null, null, new Color(115,164,209), Color.WHITE);
    
        public JXXTable(CustomTableModel model){ 
            super(model); 
            this.setDefaultRenderer(Object.class, renderer);
        }
        public void Paint(){
            this.setDefaultRenderer(Object.class, renderer);
            this.setHighlighters();
            this.addHighlighter(cellHighlight); 
        }
    }