I notice if you set a new model into a JTable, if the number of rows is lower than the highest selected row, this can happen:
java.lang.IndexOutOfBoundsException: Row index 2 is out of bounds (0..1)
at com.acme.MyTableModel.getRowObject(MyTableModel.java:184)
at com.acme.MyCellRenderer.getTableCellRendererComponent(MyCellRenderer.java:144)
at javax.swing.JTable$AccessibleJTable.getAccessibleChild(JTable.java:7039)
at javax.swing.JTable$AccessibleJTable.getAccessibleAt(JTable.java:7426)
at javax.swing.JTable$AccessibleJTable.valueChanged(JTable.java:6939)
at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:184)
at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:164)
at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:211)
at javax.swing.DefaultListSelectionModel.changeSelection(DefaultListSelectionModel.java:405)
at javax.swing.DefaultListSelectionModel.changeSelection(DefaultListSelectionModel.java:415)
at javax.swing.DefaultListSelectionModel.removeSelectionIntervalImpl(DefaultListSelectionModel.java:576)
at javax.swing.DefaultListSelectionModel.clearSelection(DefaultListSelectionModel.java:420)
at javax.swing.JTable.clearSelection(JTable.java:2117)
at javax.swing.JTable.clearSelectionAndLeadAnchor(JTable.java:2125)
at javax.swing.JTable.tableChanged(JTable.java:4370)
at javax.swing.JTable.setModel(JTable.java:3688)
Whose bug is this? Should Swing be checking this index before asking for a renderer, or is it the renderer's job to check the index?
Edit:
Here's a much reduced example, just in case there really is a bug elsewhere. Press the button until row 5 is visible, then select row 5 and press the button again.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableRowSorter;
public class BreakMyTable implements Runnable {
JTable table;
public static void main(String[] args) {
SwingUtilities.invokeLater(new BreakMyTable());
}
#Override
public void run() {
table = new JTable();
installNewModel();
TableColumnModel columnModel = new DefaultTableColumnModel();
TableColumn column1 = new TableColumn(0, 100);
column1.setCellRenderer(new CustomRenderer(table.getDefaultRenderer(Integer.class)));
columnModel.addColumn(column1);
TableColumn column2 = new TableColumn(1, 400);
columnModel.addColumn(column2);
table.setColumnModel(columnModel);
table.setAutoCreateColumnsFromModel(false);
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
toolBar.add(new AbstractAction("Replace model") {
#Override
public void actionPerformed(ActionEvent e) {
installNewModel();
}
});
JFrame frame = new JFrame();
frame.setLayout(new BorderLayout());
frame.add(toolBar, BorderLayout.PAGE_START);
frame.add(new JScrollPane(table), BorderLayout.CENTER);
frame.setSize(500, 400);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public void installNewModel() {
int rows = table.getModel() == null ? 5 :
table.getModel().getRowCount() == 5 ? 6 : 5;
DefaultTableModel model = new CustomModel(rows, 2);
for (int row = rows - 1; row >= 0; row--) {
model.setValueAt(row, row, 0);
}
table.setModel(model);
table.setRowSorter(new TableRowSorter<>(model));
}
private class CustomModel extends DefaultTableModel {
public CustomModel(int rowCount, int columnCount) {
super(rowCount, columnCount);
}
// In the real implementation, getValueAt() is implemented by calling getRowObject().
public Object getRowObject(int row) {
if (row < 0 || row >= getRowCount()) {
throw new IndexOutOfBoundsException("Out of bounds: " + row);
}
return getValueAt(row, 0);
}
}
private class CustomRenderer implements TableCellRenderer {
private final TableCellRenderer delegateRenderer;
private CustomRenderer(TableCellRenderer delegateRenderer) {
this.delegateRenderer = delegateRenderer;
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int modelIndex = table.convertRowIndexToModel(row);
Object rowObject = ((CustomModel) table.getModel()).getRowObject(modelIndex);
// Would usually use the row object to decorate the cell somehow, e.g. add an icon.
return delegateRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
}
I can reproduce it quite reliably and I already know that a solution is to dodge the NPE in the renderer. I just want to know whether the values passed to the renderer are supposed to be checked before I receive them. If they are, then I will consider my solution to be a workaround and will report the bug upstream. If the renderer is supposed to check them, I will consider my solution to be a bugfix.
Related
I have a row header inside a JScrollPane of which I should set the width according to the maximum width that the text takes. I managed to do so but I have to add 1 in my call to setPreferredScrollableViewportSize in order to display the full text. Did I get the measurement wrong? Or is this quantity fixed?
This is the main class.
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.WindowConstants;
public class Example {
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
JTable table = new JTable(5, 3);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
HeaderColumn ch = new HeaderColumn(5, 1);
JFrame frame = new JFrame();
JScrollPane sp = new JScrollPane(table);
sp.setRowHeaderView(ch);
frame.add(sp);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.pack();
frame.setVisible(true);
ch.adjustSize();
}
});
}
}
This is the header class. Please look at adjustSize().
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FontMetrics;
import javax.swing.BorderFactory;
import javax.swing.JTable;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
public class HeaderColumn extends JTable{
private final int INSET = 2;
public HeaderColumn(int r, int c){
super(r,c);
}
#Override
public void columnAdded(TableColumnModelEvent e){
DefaultTableModel m = (DefaultTableModel)getModel();
int rowCount = m.getRowCount();
for(int i = 0; i < rowCount; i++){
m.setValueAt("#" + Integer.toString(i+1), i, 0);
}
TableColumn tc = getColumnModel().getColumn(e.getToIndex());
tc.setCellRenderer(new DefaultTableCellRenderer(){
#Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
setText((value == null) ? "" : value.toString());
setBorder(BorderFactory.createEmptyBorder(0, INSET, 0, INSET));
return this;
}
});
}
public void adjustSize(){
FontMetrics fm = getGraphics().getFontMetrics(getTableHeader().getFont());
int maxTextWidth = 0;
DefaultTableModel m = (DefaultTableModel)getModel();
int rowCount = m.getRowCount();
for(int i = 0; i < rowCount; i ++){
int width = fm.stringWidth(m.getValueAt(i, 0).toString());
if(width > maxTextWidth){
maxTextWidth = width;
}
}
setPreferredScrollableViewportSize(new Dimension(maxTextWidth+INSET*2+1, 0));
}
}
I managed to do so but I have to add 1 in my call to setPreferredScrollableViewportSize in order to display the full text. Did I get the measurement wrong? Or is this quantity fixed?
I think you need to include the getIntercellSpacing() value returned from the table.
Check out Table Column Adjuster for more information and example code for determining the column width.
I'm trying to create a jComboBox in a certain cell from a jTable. If on the same line in column 4 you have the value "FN", you will have on the column 5 a jComboBox with 3 options ("SSAA-MM-JJ", "SSAA/MM/JJ", "SAAMMJJ"), but all the other cells on column 5 must remain untouched if the value from the cell on column 4 on the same row is not "FN".
What do I do wrong?
Here is what i've tried:
package rdjcsv;
import java.awt.Component;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.TableCellEditor;
/**
*
* #author acucu
*/
class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
DefaultCellEditor other = new DefaultCellEditor(new JTextField());
DefaultCellEditor checkbox = new DefaultCellEditor(new JComboBox(new Object[] {"abc"}));
private DefaultCellEditor lastSelected;
#Override
public Object getCellEditorValue() {
return lastSelected.getCellEditorValue();
}
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
if(column == 4 && table.getValueAt(row, column-1).toString().contains("FN")){
if(row == 0) {
lastSelected = checkbox;
return checkbox.getTableCellEditorComponent(table, value, isSelected, row, column);
}
lastSelected = other;
return other.getTableCellEditorComponent(table, value, isSelected, row, column);
}
return other.getTableCellEditorComponent(table, value, isSelected, row, column);
}
}
And the call:
String[] values = new String[] {"SSAA-MM-JJ", "SSAA/MM/JJ", "SAAMMJJ"};
TableColumn col = jTable1.getColumnModel().getColumn(4);
col.setCellEditor(new MyComboBoxEditor(values));
col.setCellRenderer(new MyComboBoxRenderer(values));
The output:
jComboBox es on every cell from the 5th column.
Your image is showing the output from the cell renderer, not the cell editor, since only one cell editor should be visible at any time. You don't want the renderer to look like a JComboBox but rather to display as text, as a label. This suggests other problems with your program.
Other issues:
Your code above risks a NPE since lastSelected can be null when it starts out.
Why are you checking that row == 0? Do you to use the JComboBox editor for the first row only?
Post your minimal example program if still stuck,
for example, mine:
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
#SuppressWarnings("serial")
public class ComboEditorEg extends JPanel {
private MyTableModel model = new MyTableModel();
private JTable table = new JTable(model);
public ComboEditorEg() {
for (int i = 0; i < 10; i++) {
String textA = i % 2 == 0 ? "SA" : "FN";
String textB = i % 2 == 0 ? "A" : "B";
Object[] row = new String[] { textA, textB };
model.addRow(row);
}
table.getColumnModel().getColumn(1).setCellEditor(new MyCellEditor());
setLayout(new BorderLayout());
add(new JScrollPane(table));
}
private static void createAndShowGui() {
ComboEditorEg mainPanel = new ComboEditorEg();
JFrame frame = new JFrame("ComboEditorEg");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
createAndShowGui();
});
}
}
#SuppressWarnings("serial")
class MyTableModel extends DefaultTableModel {
public static final String[] COL_NAMES = { "Foo 1", "Foo 2" };
public MyTableModel() {
super(COL_NAMES, 0);
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
}
#SuppressWarnings("serial")
class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
DefaultCellEditor other = new DefaultCellEditor(new JTextField());
DefaultCellEditor checkbox = new DefaultCellEditor(new JComboBox<String>(new String[] { "abc",
"def", "ghi" }));
private DefaultCellEditor lastSelected = other; // so it's not null
#Override
public Object getCellEditorValue() {
return lastSelected.getCellEditorValue();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
if (table.getValueAt(row, column - 1).toString().contains("FN")) {
lastSelected = checkbox;
return checkbox.getTableCellEditorComponent(table, value, isSelected, row, column);
}
return other.getTableCellEditorComponent(table, value, isSelected, row, column);
}
}
I am working with netbeans IDE7.4, I am adding rows to the JTable at run-time and now I want to set background color for a particular row.
Now the problem is that when the value of that row is changed the color of that particular row is not changed and when I scroll up or down the table the changes are applied.
How to refresh the table at run-time? How to set background color of particular row at runtime?
This is renderer class am using for coloring particular row:
public class MyCellRenderer extends javax.swing.table.DefaultTableCellRenderer
{
public java.awt.Component getTableCellRendererComponent(javax.swing.JTable table, java.lang.Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
final java.awt.Component cellComponent = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Object val=table.getValueAt(row, 2);
String sval=val.toString();
sval=sval.replaceAll(":","");
int ival=Integer.parseInt(sval);
if(ival==0)
{
cellComponent.setForeground(Color.black);
cellComponent.setBackground(Color.red);
}
else
{
cellComponent.setBackground(Color.white);
cellComponent.setForeground(Color.black);
}
if (isSelected)
{
cellComponent.setForeground(table.getSelectionForeground()); cellComponent.setBackground(table.getSelectionBackground());
}
return cellComponent;
}
}
and am assigning to jtable like this :
newViewTable.setDefaultRenderer(Object.class,new MyCellRenderer());
newViewTable is the name of JTable.
how to set background color of particular row at runtime?
Use a table cell renderer. See How to Use Tables: Using Custom Renderers for details.
At some point, you need to tell the table that the content has changed in some way.
If you're using a TableModel based on AbstractTableModel, you can use the fireTableXxx events, for example fireTableCellUpdate(row, col). This will inform the JTable that the model has changed and cause it repaint the table...
You may wish to consider using fireTablesRowsUpdated as this will cause the JTable to update the entire row.
If you are using setValueAt on the model to change the value, you will need to call the appropriate event trigger...
Updated with running example
So, based on you MyCellRenderer renderer, I did this example, and it works fine...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
public class TestTable {
public static void main(String[] args) {
new TestTable();
}
public TestTable() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
final DefaultTableModel model = new DefaultTableModel(
new Object[]{"A", "B", "C"},
0
);
model.addRow(new Object[]{"A", "B", "1"});
model.addRow(new Object[]{"C", "D", "0"});
model.addRow(new Object[]{"E", "F", "1"});
model.addRow(new Object[]{"G", "H", "0"});
JTable table = new JTable(model);
table.setDefaultRenderer(Object.class, new MyCellRenderer());
JButton btn = new JButton("Add");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.addRow(new Object[]{"N", "O", (int)(Math.round(Math.random() * 1))});
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.add(btn, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MyCellRenderer extends javax.swing.table.DefaultTableCellRenderer {
public java.awt.Component getTableCellRendererComponent(javax.swing.JTable table, java.lang.Object value, boolean isSelected, boolean hasFocus, int row, int column) {
final java.awt.Component cellComponent = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Object val = table.getValueAt(row, 2);
String sval = val.toString();
sval = sval.replaceAll(":", "");
int ival = Integer.parseInt(sval);
if (ival == 0) {
cellComponent.setForeground(Color.black);
cellComponent.setBackground(Color.red);
} else {
cellComponent.setBackground(Color.white);
cellComponent.setForeground(Color.black);
}
if (isSelected) {
cellComponent.setForeground(table.getSelectionForeground());
cellComponent.setBackground(table.getSelectionBackground());
}
return cellComponent;
}
}
}
The question now is, what are you doing differently??
I have a JTable using AbstractTableModel where I have a JCheckBox in the first column for selecting rows. Now, I need to get the selected rows from the table which are checked. Right now, I am sequentially traversing from first row to the last row and getting all the rows that are selected like the following,
List<Integer> selectedRows = new ArrayList<Integer>();
for(int i = 0; i < table.getRowCount(); i++) {
if((Boolean) table.getValuAt(i, 0)) {
selectedRows.add(i);
}
}
The problem here is, I need to traverse all the rows when ever I need to get the selected rows. Right now I am having 10 to 20 rows. But in future I will get around 5000 rows. My question is, if there are 5000 rows and if the user selects only 5000nd (last record) row then I need to traverse all the 5000 rows to get the selected row. Which I think is not a good approach.
One approach which I want to implement is, to add a listener to the JCheckBox column, such that when ever there is a change (SELECTED/DESELECTED) then I need to update my array of the selected rows in the listener class. In this listener class when ever user selectes a JCheckBox I need to call table.getSelectedRow(..) and I need to store if that JCheckBox is selected.
Are there any better approaches ?
In the example below, the TableModel updates a Set<Integer> checked in the implementation of setValueAt(). The model of an adjacent JList listens to the table's model and displays the currently selected row numbers. The example assumes that the number of selected rows is small compared to the number of rows. Note the use of TreeSet, whose iterator retains the natural order of the elements.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
/** #see http://stackoverflow.com/a/13919878/230513 */
public class CheckTable {
private static final CheckModel model = new CheckModel(5000);
private static final JTable table = new JTable(model) {
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(150, 300);
}
};
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("CheckTable");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new GridLayout(1, 0));
f.add(new JScrollPane(table));
f.add(new DisplayPanel(model));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
private static class DisplayPanel extends JPanel {
private DefaultListModel dlm = new DefaultListModel();
private JList list = new JList(dlm);
public DisplayPanel(final CheckModel model) {
super(new GridLayout());
this.setBorder(BorderFactory.createTitledBorder("Checked"));
this.add(new JScrollPane(list));
model.addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
dlm.removeAllElements();
for (Integer integer : model.checked) {
dlm.addElement(integer);
}
}
});
}
}
private static class CheckModel extends AbstractTableModel {
private final int rows;
private List<Boolean> rowList;
private Set<Integer> checked = new TreeSet<Integer>();
public CheckModel(int rows) {
this.rows = rows;
rowList = new ArrayList<Boolean>(rows);
for (int i = 0; i < rows; i++) {
rowList.add(Boolean.FALSE);
}
}
#Override
public int getRowCount() {
return rows;
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public String getColumnName(int col) {
return "Column " + col;
}
#Override
public Object getValueAt(int row, int col) {
if (col == 0) {
return row;
} else {
return rowList.get(row);
}
}
#Override
public void setValueAt(Object aValue, int row, int col) {
boolean b = (Boolean) aValue;
rowList.set(row, b);
if (b) {
checked.add(row);
} else {
checked.remove(row);
}
fireTableRowsUpdated(row, row);
}
#Override
public Class<?> getColumnClass(int col) {
return getValueAt(0, col).getClass();
}
#Override
public boolean isCellEditable(int row, int col) {
return col == 1;
}
}
}
I agree with kleopatra. When you create a subclass of the AbstractTableModel, you'll override the setValue( Object value, int rowIndex, int colIndex ). In your overridden method, you just check if the column is the one with your check box, and if so, update the internal data structure appropriately. You can also add a method getCheckedRows() that returns a List< Integer > with the rows in which the check boxes have been selected.
Widgets in JTable columns are expected to be not distinguishable from normal ones, right? There seems to be behavioral difference, take Swing documentation example and move mouse over checkboxes in the Vegetarian column... They don't react at all. I understand that those are just widget surrogates, so highlighting has to be done manually, so how would I fix this? I tried widget.requestFocusInWindow();
in mouseMoved() for the surrogate widget event handler without success. Any other workaround?
You can create your own cell renderer that applies a rollover effect. Then, add a mouse listener that tracks the mouse movement and repaints the relevant cells. You need to apply the effect to the current cell under the cursor and clear the previous cell.
Below is a short example that demonstrates this approach on a checkbox renderer. The example extends default BooleanRenderer. The only change is getModel().setRollover(...) in getTableCellRendererComponent().
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.UIResource;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
public class TableRolloverDemo {
private static void createAndShowGUI() {
JFrame frame = new JFrame("TableRolloverDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTable table = new JTable();
final DefaultTableModel model = new DefaultTableModel(new Object[][] {
{ false }, { false }, { true }, { true } },
new Object[] { "Column" }) {
public Class<?> getColumnClass(int columnIndex) {
return Boolean.class;
}
};
RolloverMouseAdapter rolloverAdapter = new RolloverMouseAdapter(table);
RolloverBooleanRenderer renderer = new RolloverBooleanRenderer(rolloverAdapter);
table.addMouseListener(rolloverAdapter);
table.addMouseMotionListener(rolloverAdapter);
table.setDefaultRenderer(Boolean.class, renderer);
table.setModel(model);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
static class RolloverMouseAdapter extends MouseAdapter {
private int row = -1;
private int column = -1;
private JTable table;
public RolloverMouseAdapter(JTable table) {
this.table = table;
}
public boolean isRolloverCell(int row, int column) {
return this.row == row && this.column == column;
}
#Override
public void mouseMoved(MouseEvent e) {
int lastRow = row;
int lastColumn = column;
row = table.rowAtPoint(e.getPoint());
column = table.columnAtPoint(e.getPoint());
if (row == lastRow && column == lastColumn)
return;
if (row >= 0 && column >= 0) {
table.repaint(table.getCellRect(row, column, false));
}
if (lastRow >= 0 && lastColumn >= 0) {
table.repaint(table.getCellRect(lastRow, lastColumn, false));
}
}
#Override
public void mouseExited(MouseEvent e) {
if (row >= 0 && column >= 0) {
table.repaint(table.getCellRect(row, column, false));
}
row = column = -1;
}
}
static class RolloverBooleanRenderer extends JCheckBox implements
TableCellRenderer, UIResource {
private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
private RolloverMouseAdapter adapter;
public RolloverBooleanRenderer(RolloverMouseAdapter adapter) {
super();
this.adapter = adapter;
setHorizontalAlignment(JLabel.CENTER);
setBorderPainted(true);
}
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
getModel().setRollover(adapter.isRolloverCell(row, column));
if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
setSelected((value != null && ((Boolean) value).booleanValue()));
if (hasFocus) {
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
} else {
setBorder(noFocusBorder);
}
return this;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
It's rollover (or lack of thereof) which makes widgets in the table to appear dead. If JPanel is genuine container of widgets, why JTable and JTree are not?
Both JTable and JTree use the flyweight pattern for rendering. The default renderer & editor have no intrinsic mouse-over behavior. You have to supply the desired behavior yourself, as #Max shows.
Jtable dont put real components into cells. They only use the component's paint methods to render the cell content.