Tablecellrenderer and How to Refresh Cell Background Without Using Jtable.Repaint()

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

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

JTable: how to achieve custom rollover effect with TableCellRenderer

So, for a particular column of your table, you want to paint a border on the cell that is hovered by the mouse (only the hovered cell, only in this column).

(edit: after clarification it appears that this question has been asked before -- I'm leaving my answer below as it might still help)

  • don't change the cell renderer dynamically, have only 1 renderer for that column, and handle that situation within the single renderer.

  • don't add listeners on the Component that is returned by the renderer: such listeners won't be triggered, as the component is only used for its paint()-ing logic.

  • instead, add a mouse motion listener on the table itself, and compute the coordinates of hovered cells with JTable's methods rowAtPoint and columnAtPoint, when mouse moves over table, or exits the area.

  • (irrelevant to problem at hand, but deserves a mention) Avoid creating a new JLabel for each call of your renderer, this is wasteful. Swing is single-thread, it's safe to reuse the same object (provided you don't forget to reset all its properties that might have changed between 2 calls)

Small demo that shows the effect:

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class SimpleTableDemo extends JPanel {

public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(SimpleTableDemo::createAndShowGUI);
}

private int
hoveredRow = -1,
hoveredColumn = -1;

SimpleTableDemo() {
super(new GridLayout(1,0));

String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};

Object[][] data = {
{"Kathy", "Smith", "Snowboarding", 5, Boolean.FALSE},
{"John", "Doe", "Rowing", 3, Boolean.TRUE},
{"Sue", "Black", "Knitting", 2, Boolean.FALSE},
{"Jane", "White", "Speed reading", 20, Boolean.TRUE},
{"Joe", "Brown", "Pool", 10, Boolean.FALSE}
};

final JTable table = new JTable(data, columnNames);
table.setPreferredScrollableViewportSize(new Dimension(500, 70));
table.setFillsViewportHeight(true);
table.getColumn("Sport").setCellRenderer(new MyCellRenderer());

table.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
hoveredRow = table.rowAtPoint(p);
hoveredColumn = table.columnAtPoint(p);
table.repaint();
}
public void mouseExited(MouseEvent e) {
hoveredRow = hoveredColumn = -1;
table.repaint();
}
});

JScrollPane scrollPane = new JScrollPane(table);

add(scrollPane);
}

private static void createAndShowGUI() {
JFrame frame = new JFrame("SimpleTableDemo");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
SimpleTableDemo newContentPane = new SimpleTableDemo();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}

private class MyCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (hoveredColumn == column && hoveredRow == row) {
label.setBorder(BorderFactory.createLineBorder(Color.GREEN, 2));
}
else {
label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
return label;
}
}

}

Note 1: I'm using the default cell renderer, unlike you, but the same idea applies. The demo above is a generic example, that will be more useful as example to keep here than a specific solution for your case (for example, in my interpretation of the problem, I understand the details about icon are irrelevant).

Note 2: In the demo I repaint the whole visible area each time, but if you want to optimize it should be possible to repaint only 2 cells, that's an entire new question, see here for help about that.

How do I correctly use custom renderers to paint specific cells in a JTable?

Add an else clause to your if:

if ((row == 0) && (column == 0)) {
d.setBackground(new java.awt.Color(255, 72, 72));
}
else {
d.setBackground(Color.WHITE);
}

Remember that the same renderer instance is used to paint all the cells.

How to change color of cells in JTable to provide an effect of animation?

Change...

int id = row * table.getRowCount() + column;
cell.setBackground(id < index ? Color.LIGHT_GRAY : null);

To...

int checkRow = index / table.getColumnCount();
int checkCol = index % table.getColumnCount();

cell.setBackground(checkRow == row && checkCol == column ? Color.LIGHT_GRAY : null);

This will calculate the row and column based on the current value of index and the compare to the cells row/column of the cell renderer

JTable change cell background at mouse click - after release change background back?

in your renderer you have to overide hasFocus

for example

Sample Image

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;

public class TableRenderer extends JFrame {

private static final long serialVersionUID = 1L;
private JTable table;
private String[] columnNames = {"Date", "String", "Centered", "Integer", "Boolean"};
private Object[][] data = {
{new Date(), "A", "A", new Integer(1), true},
{new Date(), "B", "B", new Integer(2), false},
{new Date(), "C", "C", new Integer(10), null},
{new Date(), "D", "D", new Integer(4), false}
};

public TableRenderer() {
DefaultTableModel model = new DefaultTableModel(data, columnNames);
table = new JTable(model) {

private static final long serialVersionUID = 1L;
// Returning the Class of each column will allow different
// renderers to be used based on Class

@Override
public Class getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
};
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollPane = new JScrollPane(table);
getContentPane().add(scrollPane);

// Override default renderer on a specific Class
TableCellRenderer colorRenderer = new ColorRenderer();
table.setDefaultRenderer(String.class, colorRenderer);

// Override default renderer for a specific column
DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
centerRenderer.setHorizontalAlignment(JLabel.CENTER);
table.getColumnModel().getColumn(2).setCellRenderer(centerRenderer);
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
TableRenderer frame = new TableRenderer();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

/*
** Color the focused cell
*/
private class ColorRenderer extends DefaultTableCellRenderer {

private static final long serialVersionUID = 1L;

@Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (hasFocus) {
setBackground(Color.cyan);
} else if (isSelected) {
setBackground(table.getSelectionBackground());
} else {
setBackground(table.getBackground());
}
return this;
}
}
}

Changing jtable cell color after jitem click

You need to read docs about Renderers. For proper use of it.

Next code add custom cell renderer to your column:

 TableColumn column = table.getColumnModel().getColumn(1);
column.setCellRenderer(getRenderer());

Example code for getRenderer method:

private static TableCellRenderer getRenderer() {
return new DefaultTableCellRenderer(){
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
boolean solved = false;
if (value.toString().endsWith("Solved")) {
solved = true;
value = value.toString().replace("Solved", "");
}
Component tableCellRendererComponent = super.getTableCellRendererComponent(table, value,isSelected, hasFocus, row, column);
if (solved) {
tableCellRendererComponent.setBackground(Color.GREEN);
} else {
tableCellRendererComponent.setBackground(table.getBackground());
}
return tableCellRendererComponent;
}
};
}

That method returns DefaultTableCellRenderer. Here in if-else statement you determine background for cell.

EDIT:
1) Changed getRenderer method.

2)create your popUp in next way:

 pm = new JPopupMenu();
pm.add(one);
pm.add(two);
one.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
if(row != -1)
System.out.println("sfsdf" + row);

}

});

two.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
if(row != -1){
Object valueAt = table.getValueAt(row,1);
table.setValueAt(valueAt+"Solved", row,1);
((DefaultTableModel)table.getModel()).fireTableDataChanged();
}
}

});

because you add Listeners every time you call showPopup method instead of that you must to add it only once as in my example.
row field I set as static field.

3) your method showPopup changed:

private static void showPopup(MouseEvent me) throws FontFormatException,
IOException {

// is this event a popup trigger?
if (pm.isPopupTrigger(me)) {
Point p = me.getPoint();
row = table.rowAtPoint(p);
final int col = table.columnAtPoint(p);
// if we've clicked on a row in the second column
if (row != -1 && col == 1) {
final ImageIcon progress = new ImageIcon("images/progress.png");
one.setIcon(progress);
final ImageIcon ok = new ImageIcon("images/ok.png");
two.setIcon(ok);

one.setText("In progress " + row + ".");
two.setText("Solved " + row + ".");
pm.show(table, p.x, p.y);

}

}
}

4) create your table next way table = new JTable(new DefaultTableModel(data, columnNames));

If you change your code as I recomended, when you push Solved in popUp then cell will be with green background.



Related Topics



Leave a reply



Submit