TableCellRenderer and how to refresh Cell background without using JTable.repaint()
Asked Answered
D

2

13

enter image description hereenter image description hereenter image description here

my SSCCE works correctly, repainted by JTable.repaint()

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;

public class MyTableAndRenderer {

    private JFrame frame = new JFrame();
    private JPanel panel = new JPanel();
    private String[] items = {"Item 1", "Item 2", "Item 3", "Item 4"};
    private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(items);
    private JComboBox combo = new JComboBox(comboBoxModel);
    private JPanel panel1 = new JPanel();
    private String[] columnNames = {"First Name", "Last Name", "Sport",
        "# of Years", "Vegetarian"};
    private Object[][] data = {
        {"Kathy", "Smith", "Item 1", new Integer(5), (false)},
        {"John", "Doe", "Item 1", new Integer(3), (true)},
        {"Sue", "Black", "Item 3", new Integer(2), (false)},
        {"Jane", "White", "Item 3", new Integer(20), (true)},
        {"Joe", "Brown", "Item 3", new Integer(10), (false)}
    };
    private DefaultTableModel model = new DefaultTableModel(data, columnNames) {
        private static final long serialVersionUID = 1L;

        @Override
        public Class getColumnClass(int column) {
            return getValueAt(0, column).getClass();
        }
    };
    private JTable table = new JTable(model);

    public MyTableAndRenderer() {
        panel.setBorder(new EmptyBorder(10, 0, 2, 0));
        panel.add(combo);
        //@HFOE
        /*table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table,
                    Object value, boolean isSelected, boolean hasFocus,
                    int row, int column) {
                    super.getTableCellRendererComponent(table, value, isSelected,
                        hasFocus, row, column);
                String str = combo.getSelectedItem().toString();
                if (value.toString().equalsIgnoreCase(str)) {
                    setBackground(Color.RED);
                } else {
                    setBackground(null);
                }
                return this;
            }
        });*/
        //@kleopatra
        /*table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table,
                    Object value, boolean isSelected, boolean hasFocus,
                    int row, int column) {
                String str = combo.getSelectedItem().toString();
                if (value.toString().equalsIgnoreCase(str)) {
                    setBackground(Color.RED);
                } else {
                    setBackground(null);
                }
                super.getTableCellRendererComponent(table, value, isSelected,
                        hasFocus, row, column);
                return this;
            }
        });*/
        table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table,
                    Object value, boolean isSelected, boolean hasFocus,
                    int row, int column) {
                super.getTableCellRendererComponent(table, value, isSelected,
                        hasFocus, row, column);
                String str = combo.getSelectedItem().toString();
                if (value.toString().equalsIgnoreCase(str)) {
                    setBackground(Color.RED);
                    table.repaint();
                } else {
                    setBackground(null);
                    table.repaint();
                }
                return this;
            }
        });
        table.getTableHeader().setReorderingAllowed(false);
        table.setAutoCreateRowSorter(true);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        panel1.setLayout(new GridLayout(1, 1, 10, 10));
        panel1.add(new JScrollPane(table));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel, BorderLayout.NORTH);
        frame.add(panel1);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MyTableAndRenderer fs = new MyTableAndRenderer();
            }
        });
    }
}

EDIT

@Devolus wrote Did you test what I posted? I took this snippet from my own working code, I just removed the stuff inbetween as it is not relevant for the answer. I'm using Java 6 here and this works for me.

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

    Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);

    ... determine the color value ...

    cell.setBackground(back);
    cell.setForeground(fore);
}
  • caused

enter image description here

  • doesn't matter Java6/7

from code (reason to post an SSCCE)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;

public class MyTableAndRenderer {

    private JFrame frame = new JFrame();
    private JPanel panel = new JPanel();
    private String[] items = {"Item 1", "Item 2", "Item 3", "Item 4"};
    private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(items);
    private JComboBox combo = new JComboBox(comboBoxModel);
    private JPanel panel1 = new JPanel();
    private String[] columnNames = {"First Name", "Last Name", "Sport",
        "# of Years", "Vegetarian"};
    private Object[][] data = {
        {"Kathy", "Smith", "Item 1", new Integer(5), (false)},
        {"John", "Doe", "Item 1", new Integer(3), (true)},
        {"Sue", "Black", "Item 3", new Integer(2), (false)},
        {"Jane", "White", "Item 3", new Integer(20), (true)},
        {"Joe", "Brown", "Item 3", new Integer(10), (false)}
    };
    private DefaultTableModel model = new DefaultTableModel(data, columnNames) {
        private static final long serialVersionUID = 1L;

        @Override
        public Class getColumnClass(int column) {
            return getValueAt(0, column).getClass();
        }
    };
    private JTable table = new JTable(model);

    public MyTableAndRenderer() {
        panel.setBorder(new EmptyBorder(10, 0, 2, 0));
        panel.add(combo);
        table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table,
                    Object value, boolean isSelected, boolean hasFocus,
                    int row, int column) {
                Component c = super.getTableCellRendererComponent(table, value, isSelected,
                        hasFocus, row, column);
                String str = combo.getSelectedItem().toString();
                if (value.toString().equalsIgnoreCase(str)) {
                    c.setBackground(Color.RED);
                } else {
                    c.setBackground(null);
                }
                return this;
            }
        });
        table.getTableHeader().setReorderingAllowed(false);
        table.setAutoCreateRowSorter(true);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        panel1.setLayout(new GridLayout(1, 1, 10, 10));
        panel1.add(new JScrollPane(table));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel, BorderLayout.NORTH);
        frame.add(panel1);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MyTableAndRenderer fs = new MyTableAndRenderer();
            }
        });
    }
}

EDIT2

  • from WinXp (for all Win OS don't to use Nimbus, Renderer is there very funny, never seen that, great!!! how is this possible)

enter image description here

EDIT3:

note I'm simplified code as is possible, tested before my question here, then casting Rendering Component to JComponent/JLabel doesn't works too (with JLabel.repaint()/setOpaque())

Dragonet answered 29/5, 2013 at 12:55 Comment(3)
we also had issues with jtable causing the renderer not to paint its content properly. We then deceided to use JXTable, which is offering the concept of highlighting cells with Highlighters.Vulcan
@ymene yeah, I agree with using SwingX (biased me :-) Two thingies: a) your reference is severely outdated (many changes in the Highlighter API since 0.8), unfortunately the current javadoc (of release 1.6.5-1) is not easily available, only via downloading the doc from maven b) internally, a higlighter fires a changeEvent when its decoration changes, which in turn triggers the table to repaint itself :)Nyctophobia
just to emphasize what you learned and for future readers: the initial SSCCE does it SEVERELY WRONG - as in never-ever re-trigger a paint while in the painting cycle, so never-ever call repaint in getXXCellRendererNyctophobia
T
9

The issue occurs when you change the selected item. You have some implicit interaction between your combobox and you table (the selected item of the combo box influences the way the table is painted).

When the comboboxpopup is hidden, it automatically triggers a repaint of the hovered area (the RepaintManager will only repaint the hovered area, not the complete table). But in the meantime, you have changed the way the cells of the table are painted (the first cells are no longer painted in red because they don't match the selection anymore). However, the repaint manager forces to repaint only a small area of the table which does not cover completely the red cells, hence you see those visual glitches.

Here are 2 solutions I can come up with:

  • Add an ActionListener to the combobox and invoke table.repaint() (easy to do)
  • Change your table model and call fireTableCellUpdated(row, column) for the relevant cells.

SSCCE for the second solution:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;

public class MyTableAndRenderer {

    private final class DefaultTableModelExtension extends DefaultTableModel {
        private static final long serialVersionUID = 1L;

        private String selected;

        private DefaultTableModelExtension(Object[][] data, Object[] columnNames) {
            super(data, columnNames);
        }

        @Override
        public Class getColumnClass(int column) {
            return getValueAt(0, column).getClass();
        }

        public String getSelected() {
            return selected;
        }

        public void setSelected(String selected) {
            if (this.selected == null && selected == null || this.selected != null && this.selected.equalsIgnoreCase(selected)) {
                return;
            }
            class Cell {
                public final int row;
                public final int column;

                public Cell(int row, int column) {
                    super();
                    this.row = row;
                    this.column = column;
                }
            }
            List<Cell> updatedCells = new ArrayList<Cell>();
            if (this.selected != null) {
                for (int i = 0; i < data.length; i++) {
                    Object[] o = data[i];
                    for (int j = 0; j < o.length; j++) {
                        Object object = o[j];
                        if (this.selected.toString().equalsIgnoreCase(object.toString())) {
                            updatedCells.add(new Cell(i, j));
                        }
                    }
                }
            }
            this.selected = selected;
            if (this.selected != null) {
                for (int i = 0; i < data.length; i++) {
                    Object[] o = data[i];
                    for (int j = 0; j < o.length; j++) {
                        Object object = o[j];
                        if (this.selected.toString().equalsIgnoreCase(object.toString())) {
                            updatedCells.add(new Cell(i, j));
                        }
                    }
                }
            }
            for (Cell pair : updatedCells) {
                fireTableCellUpdated(pair.row, pair.column);
            }
        }
    }

    private JFrame frame = new JFrame();
    private JPanel panel = new JPanel();
    private String[] items = { "Item 1", "Item 2", "Item 3", "Item 4" };
    private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(items);
    private JComboBox combo = new JComboBox(comboBoxModel);
    private JPanel panel1 = new JPanel();
    private String[] columnNames = { "First Name", "Last Name", "Sport", "# of Years", "Vegetarian" };
    private Object[][] data = { { "Kathy", "Smith", "Item 1", new Integer(5), false }, { "John", "Doe", "Item 1", new Integer(3), true },
            { "Sue", "Black", "Item 3", new Integer(2), false }, { "Jane", "White", "Item 3", new Integer(20), true },
            { "Joe", "Brown", "Item 3", new Integer(10), false } };
    private DefaultTableModelExtension model = new DefaultTableModelExtension(data, columnNames);
    private JTable table = new JTable(model);

    public MyTableAndRenderer() {
        panel.setBorder(new EmptyBorder(10, 0, 2, 0));
        panel.add(combo);
        combo.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                updateSelected();
            }

        });
        // Need first synch
        updateSelected();
        table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row,
                    int column) {
                Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                String str = combo.getSelectedItem().toString();
                if (value.toString().equalsIgnoreCase(str)) {
                    c.setBackground(Color.RED);
                } else {
                    c.setBackground(null);
                }
                return this;
            }
        });
        table.getTableHeader().setReorderingAllowed(false);
        table.setAutoCreateRowSorter(true);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        panel1.setLayout(new GridLayout(1, 1, 10, 10));
        panel1.add(new JScrollPane(table));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel, BorderLayout.NORTH);
        frame.add(panel1);
        frame.pack();
        frame.setVisible(true);
    }

    private void updateSelected() {
        model.setSelected((String) combo.getSelectedItem());
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MyTableAndRenderer fs = new MyTableAndRenderer();
            }
        });
    }
}
Thessalonian answered 29/5, 2013 at 14:43 Comment(6)
+1 appreciated, right catch about repainting only once time in Listener, but reason of question is without JTable.repaint, btw not if fireTableCellUpdated not required to refresh customs editor an renderer too, I see this notice a few times by camickr and kleopatra tooDragonet
@Dragonet The repaint triggered by hiding of the comboboxpopup will call paint with a clipped Graphics which does not cover complete cells (only a sub-area of the table is repainted). The problem is that the "painting-state" of the table also changes (the custom renderer does not paint the cells the same way).Thessalonian
+1 for option two; Color.RED is a view attribute, but identifying which cells to color is part of the model.Macle
@Guillaume Polet now I see, accepted..., thank you for notifier based on your real knowledges, crazy as you areDragonet
+1, nice track-down and explanation - though I disagree ever so slightly with @trashgod: state that effects visual appearance is not necessarily directly inherent to the model itself (think f.i. in highlighting different aspects of the same the model in different tables)Nyctophobia
@kleopatra: poor phrasing on my part; I meant to suggest that color can sometimes be a property of the model, e.g. a traffic light or status indicator.Macle
D
0

To fix your particular problem you can add the table.repaint() as shown.

However, to properly redraw the table, you should refresh it from the outside. In this example you should add an event listener on the combobox and then refresh it from there.

public Component getTableCellRendererComponent(JTable table,
        Object value,
        boolean isSelected,
        boolean hasFocus, 
        int row, int column) 
{
    Component cell = super.getTableCellRendererComponent(table, value, isSelected,
            hasFocus, row, column);
    String str = combo.getSelectedItem().toString();
    if (value.toString().equalsIgnoreCase(str))
    {
        cell.setBackground(Color.RED);
    }
    else
    {
        cell.setBackground(null);
    }

    return cell;
}
Displace answered 29/5, 2013 at 13:1 Comment(10)
not I'm simplifyied code as is possible, caused with same issue on repainting, and tested with prepareRenderer tooDragonet
Did you test what I posted? I took this snippet from my own working code, I just removed the stuff inbetween as it is not relevant for the answer. I'm using Java 6 here and this works for me.Displace
Yeah, I just tested your exmaple, and I can see whats wrong, but don't know why.Displace
I updated my answer, and it works now. In my code I never had this, but maybe it is because I do a fireTable... event from my model, so I might not have needed this.Displace
aaach ... based on value from outside correctly, without to force for repaint table.repaint(); :-)Dragonet
Yeah. Forcing the repaint in the renderer, doesn't seem a good idea to me. :)Displace
quick lesson for you, +1 for botheringDragonet
Forcing the repaint in the renderer, doesn't seem a good idea to me I can confirm that it is even a very very bad idea. This will create an infinite painting loop. You should never use this.Thessalonian
Just to get an idea of how bad this is: put a log (like System.out.println("painting");) in the getTableCellRendererComponent. You will see that it is invoked constantly.Thessalonian
-1 as an incentive to remove the repaint from the renderer code :-) It's *REALLY bad, as @GuillaumePolet already mentioned ...Nyctophobia

© 2022 - 2024 — McMap. All rights reserved.