Making a GUI in Swing (NetBeans 15, Sun JDK 19.0.1), i'm trying to set custom background color for JTable rows and encountered issues with boolean cells, and i can't seem to be able to make the background uniform across all cells. Please note that the following code tries to paint the background for the whole table, but my target is to set the background for one line at a time; this code serves the only purpose of highlighting the weird interaction between Nimbus alternate row coloring and the custom renderers i have personally encountered.
The issue seems vastly documented already, here's what i tried:
First attempt
Uncommenting the c.setOpaque(true);
line yields the following:
Second attempt, by means of the method PrepareRenderer()
as documented
and to the conclusion that Nimbus might be interfering with the intended cell rendering.
Indeed, removing Nimbus altogether gives me the result i want, but sends the rest of the UI back to the middle ages...
Oddly enough, if i select a cell, the whole row gets the correct background, including the boolean cells:
The last relevant piece of info i found is this, in the javadoc for the setBackground() method: It is up to the look and feel to honor this property, some may choose to ignore it.
which made me dubious this can even work withoud swapping Nimbus for something else.
My conclusion: no matter where i put the rendering instructions, i can only manage to change the background of the booleans that are not on one of the alternating rows, unless the row is selected.
The question: did i miss some major obvious configuration step? Or perhaps is there a way to disable Nimbus's Table alternating row colors? Or again, is this some sort of known issue?
(more SO answers: this is not relevant; this does not work;)
Edit: added SSCCE, though being GUI code made from the IDE is far from short.
package tabletest;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
public class NewJFrame extends javax.swing.JFrame {
public NewJFrame() {
initComponents();
// attempt #1
/*
jTable1.setDefaultRenderer(Boolean.class, new MyTableRenderer(jTable1.getDefaultRenderer(Boolean.class)));;
jTable1.setDefaultRenderer(String.class, new MyTableRenderer(jTable1.getDefaultRenderer(String.class)));;
*/
}
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jScrollPane1 = new javax.swing.JScrollPane();
jTable1 = new javax.swing.JTable() {
//attempt #2
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
//((JComponent)c).setOpaque(true);
c.setBackground(Color.red);
return c;
}
}
;
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jTable1.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
{ new Boolean(true), new Integer(1), new Float(0.1), "asd"},
{ new Boolean(true), new Integer(2), new Float(0.2), "lol"},
{null, new Integer(3), new Float(0.3), "xd"},
{null, new Integer(4), new Float(0.4), "ftw"},
{null, new Integer(5), new Float(0.5), "wtf"}
},
new String [] {
"bool", "int", "float", "string"
}
) {
Class[] types = new Class [] {
java.lang.Boolean.class, java.lang.Integer.class, java.lang.Float.class, java.lang.String.class
};
public Class getColumnClass(int columnIndex) {
return types [columnIndex];
}
});
jScrollPane1.setViewportView(jTable1);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(59, 59, 59)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(174, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(25, 25, 25)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(100, Short.MAX_VALUE))
);
pack();
}// </editor-fold>
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame().setVisible(true);
}
});
}
//attempt #1
class MyTableRenderer implements TableCellRenderer {
private final TableCellRenderer renderer;
public MyTableRenderer(TableCellRenderer renderer) {
this.renderer = renderer;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component c = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
//c.setOpaque(true);
c.setBackground(Color.red);
return c;
}
}
// Variables declaration - do not modify
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTable jTable1;
// End of variables declaration
}
CodePudding user response:
You wrote in your question (regarding the code in your question):
the following code tries to paint the background for the whole table
This is what I set out to do. However, you also wrote:
my target is to set the background for one line at a time
Then I tried a different approach – which still paints the entire background red but I'm hoping you will be able to adapt the below code to suit your requirements.
First Approach
As detailed here:
https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/color.html
and here:
https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/_nimbusDefaults.html
Simply setting the appropriate properties, in class UIManager
, achieves [what I believe to be] your desired result, i.e. making the entire JTable
background red. This works for all columns apart from the first column of the JTable
that displays Boolean
values. For that, I created a custom [table] cell renderer, based on the one used by Nimbus look-and-feel, namely:
javax.swing.plaf.synth.SynthTableUI.SynthBooleanTableCellRenderer
Here is the code for that renderer:
class BoolTableCellRenderer extends JCheckBox implements TableCellRenderer {
private boolean isRowSelected;
public BoolTableCellRenderer() {
setHorizontalAlignment(JLabel.CENTER);
setName("Table.cellRenderer");
}
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
isRowSelected = isSelected;
if (isSelected) {
setForeground(unwrap(table.getSelectionForeground()));
setBackground(unwrap(Color.red));
}
else {
setForeground(unwrap(table.getForeground()));
setBackground(unwrap(Color.red));
}
setSelected((value != null && ((Boolean)value).booleanValue()));
return this;
}
private Color unwrap(Color c) {
if (c instanceof UIResource) {
return new Color(c.getRGB());
}
return c;
}
public boolean isOpaque() {
return true;
}
}
I also wrote a custom TableModel
.
class PrimitivesTableModel extends DefaultTableModel {
public PrimitivesTableModel(Object[][] data, Object[] columnNames) {
super(data, columnNames);
}
public Class<?> getColumnClass(int columnIndex) {
Class<?> columnClass;
switch (columnIndex) {
case 0:
columnClass = Boolean.class;
break;
case 1:
columnClass = Integer.class;
break;
case 2:
columnClass = Float.class;
break;
case 3:
columnClass = String.class;
break;
default:
columnClass = Object.class;
}
return columnClass;
}
}
Here is the code for this first approach:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class NewsFram {
private void buildAndDisplayGui() {
JFrame frame = new JFrame("NewsFram");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createTable(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JScrollPane createTable() {
Object[][] data = new Object[][]{{Boolean.valueOf(true), Integer.valueOf(1), Float.valueOf(0.1f), "asd"},
{Boolean.valueOf(true), Integer.valueOf(2), Float.valueOf(0.2f), "lol"},
{null, Integer.valueOf(3), Float.valueOf(0.3f), "xd"},
{null, Integer.valueOf(4), Float.valueOf(0.4f), "ftw"},
{null, Integer.valueOf(5), Float.valueOf(0.5f), "wtf"}};
String[] columnNames = new String[]{"bool", "int", "float", "string"};
PrimitivesTableModel model = new PrimitivesTableModel(data, columnNames);
JTable table = new JTable(model);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setDefaultRenderer(Boolean.class, new BoolTableCellRenderer());
JScrollPane scrollPane = new JScrollPane(table);
return scrollPane;
}
public static void main(String[] args) {
UIManager.put("Table:\"Table.cellRenderer\".background", Color.red);
UIManager.put("Table.alternateRowColor", Color.red);
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
try {
UIManager.setLookAndFeel(info.getClassName());
break;
}
catch (ClassNotFoundException |
IllegalAccessException |
InstantiationException |
UnsupportedLookAndFeelException x) {
x.printStackTrace();
}
}
}
EventQueue.invokeLater(() -> new NewsFram().buildAndDisplayGui());
}
}
Second Approach
This approach does not modify UIManager
defaults but instead overrides method prepareRenderer
(in class JTable
). Note that this approach also uses the same TableModel
and custom renderer that the first approach used.
I overrode method prepareRenderer
rather than creating another custom renderer that would be based on the general JTable
cell renderer used by Nimbus look-and-feel since that renderer is much more complex than SynthBooleanTableCellRenderer
.
Here is the code for the second approach:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.UIResource;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
public class NewFrame {
private void buildAndDisplayGui() {
JFrame frame = new JFrame("NewFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createTable(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JScrollPane createTable() {
Object[][] data = new Object[][]{{Boolean.valueOf(true), Integer.valueOf(1), Float.valueOf(0.1f), "asd"},
{Boolean.valueOf(true), Integer.valueOf(2), Float.valueOf(0.2f), "lol"},
{null, Integer.valueOf(3), Float.valueOf(0.3f), "xd"},
{null, Integer.valueOf(4), Float.valueOf(0.4f), "ftw"},
{null, Integer.valueOf(5), Float.valueOf(0.5f), "wtf"}};
String[] columnNames = new String[]{"bool", "int", "float", "string"};
PrimitivesTableModel model = new PrimitivesTableModel(data, columnNames);
@SuppressWarnings("serial")
JTable table = new JTable(model) {
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Object value = getValueAt(row, column);
boolean isSelected = false;
boolean hasFocus = false;
// Only indicate the selection and focused cell if not printing
if (!isPaintingForPrint()) {
isSelected = isCellSelected(row, column);
boolean rowIsLead = (selectionModel.getLeadSelectionIndex() == row);
boolean colIsLead = (columnModel.getSelectionModel().getLeadSelectionIndex() == column);
hasFocus = (rowIsLead && colIsLead) && isFocusOwner();
}
Component cmpt = renderer.getTableCellRendererComponent(this,
value,
isSelected,
hasFocus,
row,
column);
if (!(value instanceof Boolean)) {
cmpt.setBackground(Color.red);
}
return cmpt;
}
};
table.setDefaultRenderer(Boolean.class, new BoolTableCellRenderer());
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
JScrollPane scrollPane = new JScrollPane(table);
return scrollPane;
}
public static void main(String[] args) {
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
try {
UIManager.setLookAndFeel(info.getClassName());
break;
}
catch (ClassNotFoundException |
IllegalAccessException |
InstantiationException |
UnsupportedLookAndFeelException x) {
x.printStackTrace();
}
}
}
EventQueue.invokeLater(() -> new NewFrame().buildAndDisplayGui());
}
}