JTable valuechanged then change cell color - java

I have here a JTable with two(2) columns. The right column is an editable one while the other is not.
So, what my problem is that whenever the user changed the value of a cell, that specific cell will changed its cell color.
I wanna do this because I want to let the user know that he/she made some changes in the table.
I found this somewhere and it somehow solved my problem but 1 thing that didn't come up with my expectation is that after changing the value and clicked another cell, the color changes back to its original color. I want to let it stay until it is saved.
#Override
public Component prepareEditor(TableCellEditor editor, int data, int columns) {
Component c = super.prepareEditor(editor, data, columns);
c.setBackground(Color.RED);
return c;
}
Is it possible? If yes, please show some example.
UPDATE:
String[] columnname = {"Student Name", "Grade"};
Object[][] data = {};
gradetable = new JTable(data, columnname){
private Object[][] rowData;
public boolean isCellEditable(int data, int columns){
return columns == 1;
}
public Component prepareRenderer(TableCellRenderer r, int data, int columns){
final Component c = super.prepareRenderer(r, data, columns);
if (data % 2 == 0){
c.setBackground(Color.LIGHT_GRAY);
}
else{
c.setBackground(Color.WHITE);
}
if (isCellSelected(data, columns)){
c.setBackground(Color.ORANGE);
}
return c;
}
#Override
public Component prepareEditor(TableCellEditor editor, int data, int columns) {
Component c = super.prepareEditor(editor, data, columns);
c.setBackground(Color.RED);
return c;
}
};
gradetable.setModel(new DefaultTableModel(data, columnname));
gradetable.setPreferredScrollableViewportSize(new Dimension (350, 130));
gradetable.setFillsViewportHeight(true);
gradetable.getTableHeader().setReorderingAllowed(false);
gradetable.setGridColor(new Color(128,128,128,128));
JScrollPane jsp = new JScrollPane(gradetable);
panel3.add(jsp);

Tables use a TableCellRenderer to paint values on the screen. The editors and renderers don't actually have anything to do with each other (from a painting point of view).
So once the editor has been dismissed (accepted or cancelled), the cell is repainted using the assigned TableCellRenderer
You need to supply, in your table model, some way to determine which rows have been updated and change the state of the renderer to match.
FYI- The DefaultTableCellRenderer uses a JLabel as it's base renderer, so it is transparent by default; you will need to make it opaque to make it render properly.
Check out Using custom renderers for more details
Update with example
This is nothing more then a proof of concept. It will not meet your absolute requirements and you should take a serious look at the tutorial linked above.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
public class TableEdit {
public static void main(String[] args) {
new TableEdit();
}
public TableEdit() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
JTable table = new JTable(new MyTableModel());
table.setSurrendersFocusOnKeystroke(true);
TableColumnModel model = table.getColumnModel();
model.getColumn(1).setCellRenderer(new MyTableCellRenderer());
add(new JScrollPane(table));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class MyData {
private String key;
private String value;
private boolean changed;
public MyData(String key, String value) {
this.key = key;
this.value = value;
this.changed = false;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public void setValue(String newValue) {
if (value == null ? newValue != null : !value.equals(newValue)) {
value = newValue;
changed = true;
}
}
public boolean hasChanged() {
return changed;
}
}
public class MyTableModel extends AbstractTableModel {
private List<MyData> data;
public MyTableModel() {
data = new ArrayList<>(25);
for (int index = 0; index < 5; index++) {
data.add(new MyData("A" + (index + 1), "B" + (index + 1)));
}
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
MyData myData = data.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = myData.getKey();
break;
case 1:
value = myData.getValue();
break;
}
return value;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 1;
}
public boolean hasChanged(int rowIndex) {
MyData myData = data.get(rowIndex);
return myData.hasChanged();
}
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
MyData myData = data.get(rowIndex);
myData.setValue(aValue == null ? null : aValue.toString());
}
}
public class MyTableCellRenderer extends 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);
setOpaque(isSelected);
TableModel model = table.getModel();
if (model instanceof MyTableModel) {
MyTableModel myModel = (MyTableModel) model;
if (myModel.hasChanged(row)) {
if (!isSelected) {
setBackground(Color.RED);
setOpaque(true);
}
}
}
return this;
}
}
}

Related

JTable change color for entire column when values changed - Swing

I tried to find here for a long time answer for my question but without the exact result i expected.
I have JTable which every time i am changing values in entire column (only in one column every time).
I want to listen to a table changes and when data changes in the column, the color in the column will be changed too and all other columns will be in the default color.
This is the code for the table listener:
Class CustomCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component rendererComp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
table.getModel().addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if(***here i want to know which column changed or something like that***){
rendererComp.setBackground(Color.CYAN);
}
}
});
return rendererComp ;
}
}
and this is the code for the table creation:
private void createTable() {
tablePanel.setLayout(new FlowLayout(FlowLayout.LEFT));
DefaultTableModel tableModel = new DefaultTableModel(){
#Override
public boolean isCellEditable(int row, int column) {
//all cells false
return false;
}
};
contentTable = new JTable(tableModel);
contentTable.setGridColor(Color.LIGHT_GRAY);
for(int i=0; i<columnSize; i++) {
tableModel.addColumn("0");
}
for(int i=0; i<rawSize; i++) {
tableModel.addRow(new Object[] { "" });
}
for(int i=0; i<rawSize; i++) {
for(int j=0; j<tableModel.getRowCount(); j++) {
tableModel.setValueAt("0", j, i);
}
}
for(int i=0; i<ramSize; i++) {
contentTable.getColumnModel().getColumn(i).setCellRenderer(new CustomCellRenderer());
}
JScrollPane scrollPane = new JScrollPane(contentTable);
scrollPane.setPreferredSize(new Dimension(400, 150));
tablePanel.add(scrollPane);
}
Store the desired state in the TableModel; let the TableCellRenderer use the state to condition the view accordingly. In the example below, as soon as setValueAt() updates any cell, edited is marked true. The render, which is applied to column zero, changes the display accordingly. Note how clearEdited() invokes fireTableDataChanged() to force the table to render all cells when called in the Clear handler.
Addendum: The update below shows one approach to handling multiple columns independently. The CustomModel now contains a Map<Integer, Boolean> to store the edited state for each column to which the CustomRenderer is applied. As an aside, the CustomRenderer now invokes convertColumnIndexToModel() and sets the selection color correctly.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
/**
* #see http://stackoverflow.com/a/37439731/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CustomModel model = new CustomModel();
model.setColumnIdentifiers(new String[]{"A", "B"});
for (int i = 0; i < 16; i++) {
model.addRow(new String[]{"A:" + i, "B:" + i});
}
JTable table = new JTable(model) {
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(100, getRowHeight() * getRowCount() / 2);
}
};
table.getColumnModel().getColumn(0).setCellRenderer(new CustomRenderer(model));
table.getColumnModel().getColumn(1).setCellRenderer(new CustomRenderer(model));
f.add(new JScrollPane(table));
JPanel p = new JPanel();
p.add(new JButton(new UpdateAction("Update A", model, 0)));
p.add(new JButton(new UpdateAction("Update B", model, 1)));
p.add(new JButton(new AbstractAction("Clear") {
#Override
public void actionPerformed(ActionEvent e) {
model.clearEdited(0);
model.clearEdited(1);
}
}));
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static class CustomModel extends DefaultTableModel {
private final Map<Integer, Boolean> edited = new HashMap<>();
public boolean isEdited(int column) {
return edited.get(column) != null && edited.get(column);
}
public void clearEdited(int column) {
edited.put(column, false);
fireTableDataChanged();
}
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
#Override
public void setValueAt(Object aValue, int row, int column) {
super.setValueAt(aValue, row, column);
edited.put(column, true);
}
}
private static class CustomRenderer extends DefaultTableCellRenderer {
private final CustomModel model;
public CustomRenderer(CustomModel model) {
this.model = model;
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int col) {
Component c = super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, col);
if (model.isEdited(table.convertColumnIndexToModel(col))) {
c.setBackground(Color.cyan);
} else if (isSelected) {
c.setBackground(table.getSelectionBackground());
} else {
c.setBackground(table.getBackground());
}
return c;
}
}
private static class UpdateAction extends AbstractAction {
private final CustomModel model;
private final int column;
public UpdateAction(String name, CustomModel model, int column) {
super(name);
this.model = model;
this.column = column;
}
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < model.getRowCount(); i++) {
model.setValueAt(model.getValueAt(i, column), i, column);
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Test()::display);
}
}

Checkboxes in a JTabel column instead of boolean values using CellRenderer

I am adding a cell renderer to one of my column. Which is intended to return checkbox instead of boolean values. By doing below stuff I can able to get the checkboxes on passing boolean values but I am unable to check/uncheck the boxes.
It works fine if I override the getColumnClass() of DataTableModel.
But I need it with renderers
public class CustomRenderer
{
Table table = new JTable();
public DefaultTableModel getDtmInsurance()
{
if (dtmInsurance == null)
{
String[] columns = { "LIC ID", "Delete" };
dtmInsurance = new DefaultTableModel(columns, 0)
{
private static final long serialVersionUID = 1L;
#Override
public boolean isCellEditable(int row, int column)
{
if (column == 1)
return true;
return false;
}
};
dtmInsurance.setColumnIdentifiers(columns);
table.setModel(dtmInsurance);
Object[] addInsurance = { "0", false };
dtmInsurance.addRow(addInsurance);
}
table.getColumnModel().getColumn(1).setCellRenderer(new MyRenderer());
return dtmInsurance;
}
class MyRenderer extends DefaultTableCellRenderer
{
private static final long serialVersionUID = 1L;
JCheckBox check = new JCheckBox();
public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected, boolean hasFocus, int row, int column)
{
Component cell = super.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);
if (obj instanceof Boolean)
{
return check;
}
return cell;
}
}
}
Now, you could go through the exercise of implementating your own renderer and editor OR you could let the table API do it for you.
You could just add
#Override
public Class<?> getColumnClass(int columnIndex) {
Class type = String.class;
switch (columnIndex) {
case 0:
type = Integer.class;
break;
case 1:
type = Boolean.class;
break;
}
return type;
}
To your dtmInsurance implementation and get
for free.
Otherwise you should have a look at Concepts: Editors and Renderers and Using Other Editors for more details about making it yourself :P
A custom editor might look something like...
public class MyBooleanEditor extends AbstractCellEditor implements TableCellEditor {
private JCheckBox check = new JCheckBox();
#Override
public Object getCellEditorValue() {
return check.isSelected();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof Boolean) {
check.setSelected((Boolean)value);
}
return check;
}
}
Which you would be able to use similarly to...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import javax.swing.AbstractCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
public class CustomRenderer extends JPanel {
private JTable table = new JTable();
private DefaultTableModel dtmInsurance;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new CustomRenderer());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public CustomRenderer() {
setLayout(new BorderLayout());
table.setModel(getDtmInsurance());
TableColumn column = table.getColumnModel().getColumn(1);
column.setCellEditor(new MyBooleanEditor());
column.setCellRenderer(new MyBooleanRenderer());
add(new JScrollPane(table));
}
public DefaultTableModel getDtmInsurance() {
if (dtmInsurance == null) {
String[] columns = {"LIC ID", "Delete"};
dtmInsurance = new DefaultTableModel(columns, 0) {
private static final long serialVersionUID = 1L;
#Override
public boolean isCellEditable(int row, int column) {
if (column == 1) {
return true;
}
return false;
}
};
dtmInsurance.setColumnIdentifiers(columns);
table.setModel(dtmInsurance);
Object[] addInsurance = {"0", false};
dtmInsurance.addRow(addInsurance);
}
return dtmInsurance;
}
class MyBooleanRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
JCheckBox check = new JCheckBox();
#Override
public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected, boolean hasFocus, int row, int column) {
Component cell = super.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);
if (obj instanceof Boolean) {
return check;
}
return cell;
}
}
public class MyBooleanEditor extends AbstractCellEditor implements TableCellEditor {
private JCheckBox check = new JCheckBox();
#Override
public Object getCellEditorValue() {
return check.isSelected();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof Boolean) {
check.setSelected((Boolean)value);
}
return check;
}
}
}
But if I have 10 rows in my table, the checkbox which i selected first as soon as starting the program, only for that checkbox I can able to check/uncheck. Not for all
You cell renderer doesn't update the state of the checkbox each time it's called, it's just return a empty checkbox.
Something more like...
class MyBooleanRenderer implements TableCellRenderer {
private static final long serialVersionUID = 1L;
JCheckBox check = new JCheckBox();
#Override
public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected, boolean hasFocus, int row, int column) {
check.setSelected(false);
if (obj instanceof Boolean) {
check.setSelected((Boolean)obj);
}
if (isSelected) {
check.setForeground(table.getSelectionForeground());
check.setBackground(table.getSelectionBackground());
} else {
check.setForeground(table.getForeground());
check.setBackground(table.getBackground());
}
return check;
}
}
Seems to work

How to make JTable's header be highlighted when column selection? [duplicate]

i'm currently building a little JTable, and want to highlight the column header (and row headers - the row-header part is actually working) when a cell is selected to make it easier to find the associated names with this cell. Here is a picture:
I already tried switching out the renderer for the header with this:
table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());
But it's only called when i click on the header and always says isSelected is false.
This is the code i use for the row-names, including the highlight inside the renderer - code is not by me, i just modified it a little:
/*
* Use a JTable as a renderer for row numbers of a given main table.
* This table must be added to the row header of the scrollpane that
* contains the main table.
*/
public class RowNameTable extends JTable
implements ChangeListener, PropertyChangeListener {
private JTable main;
public RowNameTable(JTable table) {
main = table;
main.addPropertyChangeListener(this);
setFocusable(false);
setAutoCreateColumnsFromModel(false);
setModel(main.getModel());
setSelectionModel(main.getSelectionModel());
TableColumn column = new TableColumn();
column.setHeaderValue(" ");
addColumn(column);
column.setCellRenderer(new RowNameRenderer(main));
getColumnModel().getColumn(0).setPreferredWidth(table.getColumnModel().getColumn(0).getPreferredWidth());
setPreferredScrollableViewportSize(getPreferredSize());
}
#Override
public void addNotify() {
super.addNotify();
Component c = getParent();
// Keep scrolling of the row table in sync with the main table.
if (c instanceof JViewport) {
JViewport viewport = (JViewport) c;
viewport.addChangeListener(this);
}
}
/*
* Delegate method to main table
*/
#Override
public int getRowCount() {
return main.getRowCount();
}
#Override
public int getRowHeight(int row) {
return main.getRowHeight(row);
}
/*
* This table does not use any data from the main TableModel,
* so just return a value based on the row parameter.
*/
#Override
public Object getValueAt(int row, int column) {
return Integer.toString(row + 1);
}
/*
* Don't edit data in the main TableModel by mistake
*/
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
//
// Implement the ChangeListener
//
public void stateChanged(ChangeEvent e) {
// Keep the scrolling of the row table in sync with main table
JViewport viewport = (JViewport) e.getSource();
JScrollPane scrollPane = (JScrollPane) viewport.getParent();
scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
}
//
// Implement the PropertyChangeListener
//
public void propertyChange(PropertyChangeEvent e) {
// Keep the row table in sync with the main table
if ("selectionModel".equals(e.getPropertyName())) {
setSelectionModel(main.getSelectionModel());
}
if ("model".equals(e.getPropertyName())) {
setModel(main.getModel());
}
}
/*
* Borrow the renderer from JDK1.4.2 table header
*/
private static class RowNameRenderer extends DefaultTableCellRenderer {
private JTable main;
public RowNameRenderer(JTable main) {
this.main = main;
setHorizontalAlignment(JLabel.CENTER);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (table != null) {
JTableHeader header = table.getTableHeader();
if (header != null) {
setForeground(header.getForeground());
setBackground(header.getBackground());
setFont(header.getFont());
}
}
if (isSelected) {
setFont(getFont().deriveFont(Font.BOLD));
}
setText((value == null) ? "" : main.getColumnName(row));
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
return this;
}
}
}
And here we have the relevant part to create the table:
costTableModel = new CostTableModel(costCalc);
table = new JTable(costTableModel);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setCellSelectionEnabled(true);
scrollPane = new JScrollPane(table);
RowNameTable nameTable = new RowNameTable(table);
scrollPane.setRowHeaderView(nameTable);
And the class costTableModel, just for completeness sake:
public class CostTableModel extends AbstractTableModel {
private CostCalculator costCalc;
public CostTableModel(CostCalculator costCalc) {
this.costCalc = costCalc;
}
#Override
public int getRowCount() {
return costCalc.getPersonsList().size();
}
#Override
public int getColumnCount() {
return costCalc.getPersonsList().size();
}
#Override
public String getColumnName(int col) {
return costCalc.getPersonsList().get(col).getName();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
Person debtor = costCalc.getPersonsList().get(rowIndex);
Person debtee = costCalc.getPersonsList().get(columnIndex);
return costCalc.getAmountOwed(debtor, debtee);
}
#Override
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
}
Thank you for your help in advance!
A slight variant: as I read the question, the main problem is the header not updating on column selection change. Having a custom header listen to row selection changes doesn't help much for that scenario.
In fact, a JTableHeader already is listening to the ColumnModel and the model's change notification includes selection changes. Only the columnSelectionChange method is intentionally implemented to do nothing:
// --Redrawing the header is slow in cell selection mode.
// --Since header selection is ugly and it is always clear from the
// --view which columns are selected, don't redraw the header.
A custom header can simply implement to repaint (here lazy me does it in the table's factory method just to spare me the wiring to the table, you can easily make it a stand-alone class :-).
final JTable table = new JTable(new AncientSwingTeam()) {
#Override
protected JTableHeader createDefaultTableHeader() {
// subclassing to take advantage of super's auto-wiring
// as ColumnModelListener
JTableHeader header = new JTableHeader(getColumnModel()) {
#Override
public void columnSelectionChanged(ListSelectionEvent e) {
repaint();
}
};
return header;
}
};
table.setCellSelectionEnabled(true);
table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());
Also tweaked Mad's renderer a bit, using table api:
/**
* Slightly adjusted compared to #Mad
* - use table's selectionBackground
* - use table's isColumnSelected to decide on highlight
*/
public static class ColumnHeaderRenderer extends DefaultTableCellHeaderRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected,
boolean focused, int row, int column) {
super.getTableCellRendererComponent(table, value, selected, focused, row, column);
if (table.isColumnSelected(column)) {
setBackground(table.getSelectionBackground());
}
return this;
}
}
As to the observation:
always says isSelected is false
The reason is a slight quirk in BasicTableHeaderUI:
ui.selected != columnModel.selected
uiSelected is the column which will be accessible to keybindings - if the laf supports it and the header is focusOwner. Doesn't really make sense to me, but fully defining the semantics of ui and columnModel selection fell into the excitement about the new babe fx, that is got forgotten ;-)
The basic issue I had was there was no connection between the table header and the selection change. In fact, the header is really clever with it's repaints...
I ended up providing my own header, which attached a listener to the table's selection model and repainted the header on the selection changed.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import static javax.swing.SortOrder.ASCENDING;
import static javax.swing.SortOrder.DESCENDING;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
public class TestColumnHighlight {
public static void main(String[] args) {
new TestColumnHighlight();
}
public TestColumnHighlight() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JTable table = new JTable();
DefaultTableModel model = new DefaultTableModel(
new Object[]{"abc", "def", "ghi", "jkl"},
0);
model.addRow(new Object[]{0, 0, 0, 0});
model.addRow(new Object[]{0, 0, 0, 0});
model.addRow(new Object[]{0, 0, 0, 0});
model.addRow(new Object[]{0, 0, 0, 0});
model.addRow(new Object[]{0, 0, 0, 0});
table.setModel(model);
table.setTableHeader(new CustomTableHeader(table));
table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class CustomTableHeader extends JTableHeader {
public CustomTableHeader(JTable table) {
super();
setColumnModel(table.getColumnModel());
table.getColumnModel().getSelectionModel().addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
repaint();
}
});
}
#Override
public void columnSelectionChanged(ListSelectionEvent e) {
repaint();
}
}
public class ColumnHeaderRenderer extends DefaultTableHeaderCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int column) {
super.getTableCellRendererComponent(table, value, selected, focused, row, column);
int selectedColumn = table.getSelectedColumn();
System.out.println("Selected " + selectedColumn + "-" + column);
if (selectedColumn == column) {
Color bg = table.getSelectionBackground();
setBackground(bg);
setOpaque(true);
} else {
setOpaque(false);
}
return this;
}
}
public class DefaultTableHeaderCellRenderer extends DefaultTableCellRenderer {
public DefaultTableHeaderCellRenderer() {
setHorizontalAlignment(CENTER);
setHorizontalTextPosition(LEFT);
setVerticalAlignment(BOTTOM);
setOpaque(false);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
JTableHeader tableHeader = table.getTableHeader();
if (tableHeader != null) {
setForeground(tableHeader.getForeground());
}
setIcon(getIcon(table, column));
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
return this;
}
protected Icon getIcon(JTable table, int column) {
SortKey sortKey = getSortKey(table, column);
if (sortKey != null && table.convertColumnIndexToView(sortKey.getColumn()) == column) {
switch (sortKey.getSortOrder()) {
case ASCENDING:
return UIManager.getIcon("Table.ascendingSortIcon");
case DESCENDING:
return UIManager.getIcon("Table.descendingSortIcon");
}
}
return null;
}
protected SortKey getSortKey(JTable table, int column) {
RowSorter rowSorter = table.getRowSorter();
if (rowSorter == null) {
return null;
}
List sortedColumns = rowSorter.getSortKeys();
if (sortedColumns.size() > 0) {
return (SortKey) sortedColumns.get(0);
}
return null;
}
}
}

Java Swing, Trying to replace boolean check-box in a JTable with an image-icon checkbox

So I have a JTable with check-boxes. I would like to have the check-boxes contain one image when they are "checked" and another image when they are "unchecked" (i.e., display images instead of a checked or unchecked box). Is there a way to do this? I've tried fooling with the TableCellRenderer that returns a JLabel with an ImageIcon but it was not really very effective.
More specifically, when the box is checked or unchecked the right images are there, but when the user is changing the check-box state (while the mouse is down) the original checked/unchecked images appear
This is the TableCellRenderer I tried (I have also tried it with JPanels but this was ineffective as well
public class CrassusEventTableCellEyeRenderer extends JCheckBox implements TableCellRenderer {
static Icon greyEye;
static Icon blackEye;
{//STATIC CODE BLOCK
try {
greyEye = new ImageIcon(ImageIO.read(new File("icons/shittyTest.png")));
blackEye = new ImageIcon(ImageIO.read(new File("icons/blackEye.png")));
} catch (IOException e) {
greyEye = null;
blackEye = null;
}
}
public CrassusEventTableCellEyeRenderer(){
super();
this.addItemListener(new IsCheckedItemListener());
setIcon(greyEye);
}
//commented out code that I have tried in place of the IsCheckedItemListener
/*
#Override
public void setSelected(boolean sel){
super.isSelected();
if(sel)
setIcon(blackEye);
else
setIcon(greyEye);
}
*/
public class IsCheckedItemListener implements ItemListener{
#Override
public void itemStateChanged(ItemEvent e) {
if(isSelected())
setIcon(blackEye);
else
setIcon(greyEye);
}
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
System.out.println("value: "+value+", row: "+row+", column: "+column);
if(value instanceof Boolean){
setSelected(((Boolean) value).booleanValue());
}
return this;
}
}
You'll need to supply you own custom TableCellRenderer that is capable of providing the functionality you want...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
public class TestTableRenderer {
public static void main(String[] args) {
new TestTableRenderer();
}
public TestTableRenderer() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
TableModel model = new AbstractTableModel() {
#Override
public int getRowCount() {
return 2;
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return rowIndex == 0 ? true : false;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return Boolean.class;
}
};
JTable table = new JTable(model);
table.setDefaultRenderer(Boolean.class, new CustomBooleanCellRenderer());
setLayout(new BorderLayout());
add(new JScrollPane(table));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class CustomBooleanCellRenderer extends JCheckBox implements TableCellRenderer {
private ImageIcon sad;
private ImageIcon happy;
public CustomBooleanCellRenderer() {
try {
happy = new ImageIcon(ImageIO.read(getClass().getResource("/Happy.png")));
sad = new ImageIcon(ImageIO.read(getClass().getResource("/Sad.png")));
} catch (IOException ex) {
Logger.getLogger(TestTableRenderer.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void setSelected(boolean selected) {
super.setSelected(selected);
if (selected) {
setIcon(happy);
} else {
setIcon(sad);
}
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Boolean) {
boolean selected = (boolean) value;
setSelected(selected);
}
return this;
}
}
}
Now, you could just as easily use a JLabel or DefaultTableCellRenderer, check the Object value for true/false and set the icon accordingly...but where would the fun be in that ;)
Updated to include the editor...
I've rearranged the code slightly to include a cell editor...
public class CustomCheckBox extends JCheckBox {
private ImageIcon sad;
private ImageIcon happy;
public CustomCheckBox() {
try {
happy = new ImageIcon(ImageIO.read(getClass().getResource("/Happy.png")));
sad = new ImageIcon(ImageIO.read(getClass().getResource("/Sad.png")));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public void setSelected(boolean selected) {
super.setSelected(selected);
if (selected) {
setIcon(happy);
} else {
setIcon(sad);
}
}
}
public class CustomBooleanCellRenderer extends CustomCheckBox implements TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Boolean) {
boolean selected = (boolean) value;
setSelected(selected);
}
return this;
}
}
public class CustomBooleanCellEditor extends AbstractCellEditor implements TableCellEditor {
private CustomCheckBox editor;
public CustomBooleanCellEditor() {
editor = new CustomCheckBox();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof Boolean) {
boolean selected = (boolean) value;
editor.setSelected(selected);
}
return editor;
}
#Override
public Object getCellEditorValue() {
return editor.isSelected();
}
}
You can apply the in a similar way you did the renderer...
table.setDefaultEditor(Boolean.class, new CustomBooleanCellEditor());
You would have to use the
isSelected
Method to see if the box is checked or not and by that you can use a if statement and if it is checked you use
checkBox.setIcon(myIcon);
Note that u can also u an ItemListener for the task of knowing if the box is selected or not.
Question answeared using following resources:
http://www.roseindia.net/java/example/java/swing/CustomizedCheckBox.shtml
How to check that a JCheckBox is checked?
Assuming that you have just a normal JTable you may set appropriate icons in renderer and editor:
public void setIcons(Jtable table, int column, Icon icon, Icon selectedIcon) {
JCheckBox cellRenderer = (JCheckBox) table.getCellRenderer(0, column);
cellRenderer.setSelectedIcon(selectedIcon);
cellRenderer.setIcon(icon);
DefaultCellEditor cellEditor = (DefaultCellEditor) table.getCellEditor(0, column);
JCheckBox editorComponent = (JCheckBox) cellEditor.getComponent();
editorComponent.setSelectedIcon(selectedIcon);
editorComponent.setIcon(icon);
}

JTable cell renderer skips Boolean column

Below is a picture of my JTable. My problem is that the blue background every other row isn't present in the 4th column. My table model and cell renderer are also below.
Table model:
public class MyTableModel extends DefaultTableModel {
public int getColumnCount() {
return columnNames.length;
}
public String getColumnName(int column) {
return columnNames[column];
}
public int getRowCount() {
return data.length;
}
public Object getValueAt(int row, int column) {
return data[row][column];
}
public Class getColumnClass(int column) {
return (getValueAt(0, column).getClass());
}
public void setValueAt(Object value, int row, int column) {
data[row][column] = value;
}
public boolean isCellEditable(int row, int column) {
return (column == 3);
}
}
Cell Renderer:
private class CalendarRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int column) {
super.getTableCellRendererComponent(table, value, selected, focused, row, column);
setBackground(new Color(0xFFFFFF));
if (row % 2 == 1) {
setBackground(new Color(0xE8F2FE)); //light blue
}
setBorder(null);
setForeground(Color.black);
return this;
}
}
The "default" cell renderer for Boolean is a check box. If you want to change the way that this cell gets rendered, you're going to have to supply your own TableCellRenderer that implements your own logic.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class TestTableCellRenderer {
public static void main(String[] args) {
new TestTableCellRenderer();
}
public TestTableCellRenderer() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JTable table = new JTable();
table.setModel(new TableModel());
table.setDefaultRenderer(Boolean.class, new BooleanCellRenderer());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TableModel extends AbstractTableModel {
private List<Boolean> values = new ArrayList<>(25);
public TableModel() {
for (int index = 0; index < 25; index++) {
values.add((((int) Math.round(Math.random() * 1)) == 0 ? false : true));
}
}
#Override
public int getRowCount() {
return values.size();
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return values.get(rowIndex);
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return Boolean.class;
}
}
public static class BooleanCellRenderer extends JCheckBox implements TableCellRenderer {
private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
public BooleanCellRenderer() {
setLayout(new GridBagLayout());
setMargin(new Insets(0, 0, 0, 0));
setHorizontalAlignment(JLabel.CENTER);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Boolean) {
setSelected((Boolean) value);
}
if (!isSelected) {
setBackground(new Color(0xFFFFFF));
if (row % 2 == 1) {
setBackground(new Color(0xE8F2FE)); //light blue
}
setForeground(Color.black);
} else {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
}
if (hasFocus) {
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
} else {
setBorder(noFocusBorder);
}
return this;
}
}
}
See Table Row Rendering for a solution that doesn't require you to keep creating custom renderers every time you have columns with different data.
JTable table = new JTable( model )
{
public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
{
Component c = super.prepareRenderer(renderer, row, column);
// Alternate row color
if (!isRowSelected(row))
c.setBackground(row % 2 == 0 ? getBackground() : Color.LIGHT_GRAY);
return c;
}
};

Categories