Set the button “background” of a Nimbus button

喜欢而已 提交于 2020-01-14 09:00:30

问题


I'm working on an app using the Nimbus Look and Feel. There's a table and one column contains buttons (using the Table Button Column from Rob Camick). That does work, but the result isn't what I had expected. I have tried to fix the look, but to no avail.

So the question is: how do I change the "background" (the area outside the rounded rectangle) of a Nimbus button? Preferably in a non-hacky way :-)

Using the default Table Column Button, the result looks like this:

As you can see, the background (and by this I mean the area outside the button's rounded rectangle) is wrong for the odd (white) rows. The code that produces this output is:

public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
    if (isSelected) {
        renderButton.setForeground(table.getSelectionForeground());
        renderButton.setBackground(table.getSelectionBackground());
    } else {
        renderButton.setForeground(table.getForeground());
        renderButton.setBackground(table.getBackground());
    }

    if (hasFocus) {
        renderButton.setBorder( focusBorder );
    } else {
        renderButton.setBorder( originalBorder );
    }

    // <snip some code>

    renderButton.setOpaque(true);

    return renderButton;
}

The renderButton is an instance of a default JButton. I've tried messing with the background color of the button, but that didn't work out like I expected at first:

        Color alternate = (Color)LookAndFeel.getDesktopPropertyValue("Table.alternateRowColor", Color.lightGray);
        Color normal = (Color)LookAndFeel.getDesktopPropertyValue("Table.background", Color.white);
        if (row % 2 == 0) {
            renderButton.setBackground(normal);
        } else {
            renderButton.setBackground(alternate);
        }

This produces:

So this time the buttons that look alright in the first image are now bad and vice versa. The button's inner backgrounds (the areas inside the rounded rectangles) do seem to have the correct color according to the background color property (which is what's really modified with the setBackground() call). But the area outside is all wrong. Alright, let's combine the two :

        Color alternate = table.getBackground();
        Color normal = (Color)LookAndFeel.getDesktopPropertyValue("Table.background", Color.white);
        if (row % 2 == 0) {
            renderButton.setBackground(normal);
        } else {
            renderButton.setBackground(alternate);
        }

The result:

So now the "background" does look correct, but the buttons don't look like Nimbus buttons any more. How do I make the "background" have the correct color while still looking like Nimbus buttons?


回答1:


Do not set background to JButton. Use JPanel to wrap JButton and set background to JPanel. This would be probably obvious if you used more buttons in one JTable column.

To set correct background color of JPanel i did (you should):

  1. Keep reference to original renderer
  2. Let original renderer render its own component (for every rendering)!
  3. Use background of rendered component to set background of JPanel (for every rendering)!

This way you don't have to choose correct color yourself

Also you have to override paintComponent to correctly paint white background of JPanel:

@Override
protected void paintComponent(Graphics g) {
  Color background = getBackground();
  setBackground(new Color(background.getRGB()));
  super.paintComponent(g);
}

Edit: as @kleopatra suggests you don't have to override paintComponent, only set background color as not-uiresource (shown in complete example)

Here is complete example:

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.table.TableCellRenderer;

public class Test {
public static void main(String[] args) throws Throwable {
    for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
            UIManager.setLookAndFeel(info.getClassName());
            break;
        }
    }

    String[] columnNames = new String[]{"c1"};
    Object[][] data = new Object[4][1];
    data[0][0] = "First";
    data[1][0] = "Second";
    data[2][0] = "Third";
    data[3][0] = "Fourth";

    JTable table  = new JTable(data, columnNames){
        @Override
        public javax.swing.table.TableCellRenderer getCellRenderer(int row, int column) {
            final TableCellRenderer ori = super.getCellRenderer(row, column);
            final TableCellRenderer mine = new TableCellRenderer() {
                @Override
                public Component getTableCellRendererComponent(JTable table, Object value,
                        boolean isSelected, boolean hasFocus, int row, int column) {
                    Component c = ori.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                    JPanel p = new JPanel();
                    if(value == null){
                        value = "";
                    }
                    p.add(new JButton(value.toString()));
                    p.setBackground(new Color(c.getBackground().getRGB()));
                    return p;
                }
            };
            return mine;
        };
    };
    table.setRowHeight(50);
    JFrame f = new JFrame();
    f.add(table);
    f.setVisible(true);
    f.pack();
}
}

Result:




回答2:


Below's a hacky way, following up on @Piro's suggestion: using a JPanel with the button as child component. Which in itself is a nice idea, given that we don't really want to touch the "inner" background visuals of the button.

Here the hack comes when forcing Nimbus internals to not use a JPanel's default background for filling its area but instead use the background of the given panel instance This needs relying on implementation details, particularly the lookup mechanism of a background color. That happens in SynthStyle.getColor():

// If the developer has specified a color, prefer it. Otherwise, get
// the color for the state.
Color color = null;
if (!id.isSubregion()) {
    if (type == ColorType.BACKGROUND) {
        color = c.getBackground();
    }
    ....
}

if (color == null || color instanceof UIResource) {
    // Then use what we've locally defined
    color = getColorForState(context, type);
}

Translated: it does indeed query the instance's color, but overrules it with the default if the instance color is a UIResource - which typically is the case if used as a renderer. So the trick out (tried unsuccessfully by SynthBooleanRenderer, but that's another story ;-) is to make the instance color not a UIResource. An additional quirk is that being UIResource is necessary to ensure the striping color - which is not of type UIResource, haha - be applied ... intuitive, isn't it ...

public class RendererPanel implements TableCellRenderer {

    private JComponent panel;
    private JButton button;
    public RendererPanel() {
        panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(3, 10, 2, 10));
        button = new JButton();
        panel.add(button);
    }

    @Override
    public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row,
            int column) {
        // suggestion by Piro - use background of default
        DefaultTableCellRenderer dt = (DefaultTableCellRenderer) table.getDefaultRenderer(Object.class);
        dt.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        // first try: set the color as-is - doesn't work
        // panel.setBackground(dt.getBackground());
        // second try: set color as not ui-resource
        // that's working because at this point we already have the color that will be used
        // let's hinder synth background color searching to fall back to component defaults
        panel.setBackground(new Color(dt.getBackground().getRGB()));
        // hack: unwrap ui-resource as needed
        // updateBackground(isSelected ? table.getSelectionBackground() : table.getBackground(), row);
        button.setText(String.valueOf(value));
        return panel;
    }

    private void updateBackground(Color color, int row) {
        Color hack = row % 2 == 0 ? unwrap(color) : color;
        panel.setBackground(hack);
    }

    private Color unwrap(Color c) {
        if (c instanceof UIResource) {
            return new Color(c.getRGB());
        }
        return c;
    }

}

Screenshot: with unwrap hack

Screenshot: using default colors (from the renderer installed for Object.class)

The non-hacky way out might be (didn't try here, but remember having done once) to register a Region with the style, similarly to what NimbusDefaults does internally:

register(Region.PANEL, "Table:\"Table.cellRenderer\"");

Problem here being that there's no public api to do so (or could be that I simply don't know enough about Synth ;-)



来源:https://stackoverflow.com/questions/18197505/set-the-button-background-of-a-nimbus-button

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!