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

Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • 1
    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](http://download.java.net/javadesktop/swinglabs/releases/0.8/docs/api/org/jdesktop/swingx/decorator/Highlighter.html). – crusam May 29 '13 at 13:21
  • @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 :) – kleopatra Jun 15 '13 at 08:40
  • 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 getXXCellRenderer – kleopatra Jun 15 '13 at 08:43

2 Answers2

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();
            }
        });
    }
}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • +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 too – mKorbel May 29 '13 at 14:53
  • @mKorbel 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). – Guillaume Polet May 29 '13 at 14:59
  • +1 for option two; `Color.RED` is a view attribute, but identifying which cells to color is part of the model. – trashgod May 29 '13 at 15:49
  • @Guillaume Polet now I see, accepted..., thank you for notifier based on your real knowledges, crazy as you are – mKorbel May 29 '13 at 18:59
  • 1
    +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) – kleopatra Jun 15 '13 at 08:52
  • 1
    @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. – trashgod Jun 15 '13 at 10:15
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;
}
Devolus
  • 21,661
  • 13
  • 66
  • 113
  • not I'm simplifyied code as is possible, caused with same issue on repainting, and tested with prepareRenderer too – mKorbel May 29 '13 at 13:04
  • 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. – Devolus May 29 '13 at 13:06
  • Yeah, I just tested your exmaple, and I can see whats wrong, but don't know why. – Devolus May 29 '13 at 13:16
  • 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. – Devolus May 29 '13 at 13:21
  • aaach `... based on value from outside correctly, without to force for repaint table.repaint();` :-) – mKorbel May 29 '13 at 13:23
  • Yeah. Forcing the repaint in the renderer, doesn't seem a good idea to me. :) – Devolus May 29 '13 at 13:24
  • quick lesson for you, +1 for bothering – mKorbel May 29 '13 at 13:30
  • 1
    _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. – Guillaume Polet May 29 '13 at 14:18
  • 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. – Guillaume Polet May 29 '13 at 14:45
  • 3
    -1 as an incentive to remove the repaint from the renderer code :-) It's **REALLY* bad, as @GuillaumePolet already mentioned ... – kleopatra Jun 15 '13 at 10:00