Skip to content
Advertisement

Nimbus takes precedence over manually defined renderer?

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 using a custom renderer, like so:

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

and got this result:
booleans have white background

Uncommenting the c.setOpaque(true); line yields the following: booleans have alternating background

Second attempt, by means of the method PrepareRenderer() as documented here, and i made it this way:

tbl_Correction = new JTable() {
    @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;
    }
};

got the exact same behavior as before, down to the detail of uncommenting the setOpaque line.

Further reading revealed that the alternate table row coloring is handled by the Nimbus Look&Feel, which NetBeans IDE configured automagically upon creation of the project, so i tried adding this line after the Nimbus configuration: UIManager.getLookAndFeelDefaults().put("Table.alternateRowColor", Color.GREEN); which led to this
booleans have alternating green background
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:
enter image description here

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                   
}

Advertisement

Answer

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

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement