2

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]; }
}
DarkOwl
  • 263
  • 1
  • 5
  • 15

2 Answers2

2

I "think" part of the problem has to do with you adding ANOTHER highlighter EVERY time the selection changes.

Instead, consider adding the highlight only when you initialize the table...

Having said that, there's also no need to extend the JXTable, you're not really adding any new functionality to the class. You could, instead, create a factory of some kind that configured that table you want

enter image description here

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
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.HighlightPredicate;

public class MainClass {

    public static void main(String[] args) {
        new MainClass();
    }

    public MainClass() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                CustomTableModel tableModel = new CustomTableModel();
                JXTable table = new JXTable(tableModel);
                table.setDefaultRenderer(Object.class, new CustomTableCellRenderer());
                HighlightPredicate predicate = new HighlightPredicate() {
                    @Override
                    public boolean isHighlighted(Component cmpnt, org.jdesktop.swingx.decorator.ComponentAdapter ca) {
                        System.out.println(ca.getComponent());
                        return ca.hasFocus();
                    }
                };
                table.addHighlighter(new ColorHighlighter(predicate, null, null, new Color(115, 164, 209), Color.WHITE));

                JScrollPane scrollPane = new JScrollPane(table);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(scrollPane);
                frame.setVisible(true);
                frame.pack();
            }
        });
    }

    class CustomTableCellRenderer extends DefaultTableCellRenderer {

        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            return (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        }
    }

    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];
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thank you for your suggestions. Unfortunately I require the ability to 'refresh' the highlighters on the table (... in the full program there are other highlighters also). Nevertheless, the line "this.setHighlighters()" should prepare a clean slate before re-applying them, so I'm not adding more and more all the time. (And if doing so were the cause of the issue, I'd question why only the top row is having symptoms.) – DarkOwl Oct 26 '14 at 04:05
  • @DarkOwl Have you considered changing the actually highlight logic, rather then the highligters then selves – MadProgrammer Oct 26 '14 at 04:07
  • Sorry, I'm not sure I understand what that would involve. It sounds like you're basically suggesting that I search for a work around - which I may ultimately have to resort to - but I'd still like to identify the cause of the problem above. – DarkOwl Oct 26 '14 at 12:05
  • I've fixed it now. You were right about the problem being the creation of another object on EVERY selection change, but it wasn't the highlighter object that was the problem, it was the TableCellRenderer object. – DarkOwl Oct 26 '14 at 13:40
  • It still be concerned about the recreation of the highlighters, but that's more of performance issue know ;) – MadProgrammer Oct 26 '14 at 19:49
0

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); 
    }
}
DarkOwl
  • 263
  • 1
  • 5
  • 15