3

We have a program that can change between "daytime" (high-contrast) and "nighttime" (low-contrast) modes. This is done by changing the look-and-feel on the fly. It works for almost all components, but a few are ignoring the background color changes. In particular, I've noticed buttons, comboboxes, and table headers all ignore the change in the PLAF.

Here is an example program with a button. Am I doing something wrong? Is this an OS-dependent behavior somehow? (I'm on OSX)

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.plaf.ColorUIResource;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class Demo extends JFrame {
   public static void main(String[] args) {
      new Demo();
   }

   public Demo() {
      setTitle("PLAF button test");
      final Demo thisWindow = this;
      final JButton button = new JButton("Click me!");
      button.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            Color curColor = UIManager.getColor("Button.background");
            ColorUIResource targetColor;
            if (curColor.equals(new Color(32, 32, 32))) {
               targetColor = new ColorUIResource(192, 192, 192);
            }
            else {
               targetColor = new ColorUIResource(32, 32, 32);
            }
            System.out.println("Setting new color to " + targetColor +
               " because old color was " + curColor);
            UIManager.put("Button.background", targetColor);
            SwingUtilities.updateComponentTreeUI(thisWindow);
         }
      });
      add(button);
      setMinimumSize(new java.awt.Dimension(200, 200));
      setVisible(true);
   }
}

And here is the output I get when I run this program and click on the button:

Setting new color to javax.swing.plaf.ColorUIResource[r=32,g=32,b=32] because old color was com.apple.laf.AquaImageFactory$SystemColorProxy[r=238,g=238,b=238]

Setting new color to javax.swing.plaf.ColorUIResource[r=192,g=192,b=192] because old color was javax.swing.plaf.ColorUIResource[r=32,g=32,b=32]

Setting new color to javax.swing.plaf.ColorUIResource[r=32,g=32,b=32] because old color was javax.swing.plaf.ColorUIResource[r=192,g=192,b=192]

Setting new color to javax.swing.plaf.ColorUIResource[r=192,g=192,b=192] because old color was javax.swing.plaf.ColorUIResource[r=32,g=32,b=32]

So the UIManager believes the color is changing, but the apparent color on-screen does not change.

Any advice appreciated. Thank you for your time.

Community
  • 1
  • 1
chris
  • 171
  • 10
  • I've seen Mac specific rendering issues with JButton background colors - these were via the setBackgroundColor method and not the UIManager, but the underlying cause could be the same. Sorry, don't have my mac handy now to test, but try setting the border painted property to false. See http://stackoverflow.com/questions/1065691/how-to-set-the-background-color-of-a-jbutton-on-the-mac-os – copeg Apr 27 '15 at 19:52
  • I tried adding setOpaque(false) and setBorderPainted(false), but these don't allow the background color to be changed via PLAF changes (or via manual setBackground() calls, for that matter). Also, on Windows we see similar issues with buttons, table headers, etc. – chris Apr 27 '15 at 20:05
  • Can you give the fully qualified name of the LAF (like `com.sun.java.swing.plaf.gtk.GTKLookAndFeel`) you are using? – user1803551 Apr 27 '15 at 20:08
  • I'm specifically *not* creating a new LAF from scratch because we want to use OS-appropriate components and just override specific attributes (generally, the background color). On my laptop, that means I end up with a modified-at-runtime version of com.apple.laf.AquaLookAndFeel, but it'd be different on a Windows machine or a Linux machine. – chris Apr 27 '15 at 20:15
  • @user3421216 Try to use the method `JButton.setContentAreaFilled(true)` (method is defined in parent `AbstractButton`). Probably Mac L&F set it to false, so the button don't paint the whole content. I have no Mac, so I cannot test whether it works. – Sergiy Medvynskyy Apr 27 '15 at 20:57
  • It is up to the LAF to honor `UIManager` properties, which is why I need to ask for specific names of the LAFs you are asking about. – user1803551 Apr 27 '15 at 20:59
  • Sergiy: `setContentAreaFilled` does not have any visible effect. user1803551: understood; I just wanted to be clear about my methods. – chris Apr 27 '15 at 21:01
  • (You will want to use @ as Sergiy did with you in order to notify a user that you reply to their comment, unless the comment is under their question or answer, which notifies them regardless as in your case.) – user1803551 Apr 27 '15 at 21:04

1 Answers1

3

Is this an OS-dependent behavior somehow?

The effect is OS-dependent only in the sense that the UI delegate controls the component's appearance, and each supported platform defines a default Look & Feel. On Mac OS X, the default is com.apple.laf.AquaLookAndFeel; here is the corresponding source. As shown in UIManager Defaults, cited here, the UIManager key for "Button.background" has a light-gray value of

com.apple.laf.AquaImageFactory$SystemColorProxy[r=238,g=238,b=238]

but the allotted space is completely covered by the native component. A similar effect obscures the light-pink placeholder shown for "RadioBUtton.icon". Some mitigation strategies:

  • As suggested here, you can set the background of the enclosing JPanel.

  • As suggested here, you can use setOpaque() to tint some of the background; a darker() shade may be helpful.

    JButton b = new JButton("Test");
    b.setOpaque(true);
    b.setBackground(Color.red.darker());
    
  • If you use a Border, remember to "put the component in a JPanel and set the border on the JPanel."

  • Using a control such as the one seen here, let the user choose the preferred Look & Feel; persist the choice in an instance of java.util.prefs.Preferences.

  • Direct the user to the contrast options in the Mac OS X Accessibility System Preferences panel.

  • Wrap platform-specific code in a suitable predicate.

    if (System.getProperty("os.name").startsWith("Mac OS X")) {
        // Specific to Mac OS X
    }
    
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Ahh, okay, this explains a lot. So what I actually *want* to change is the color of the "native component" in this case. The issue we're running into is in the night-time mode, where everything has a dark background, the light color of the buttons on the dark background is very stark. Is there some platform-adaptable way to figure out what the PLAF is using to render button "foregrounds" and override their colors? So on OSX I'd have an Aqua-style button with a dark color, and some suitable alternate on Windows, etc. – chris Apr 27 '15 at 22:23
  • @user3421216: Sorry, I'm not aware of such an approach; I've expanded the list of alternatives above. – trashgod Apr 28 '15 at 01:35
  • Okay, thanks for your efforts. It's looking like realistically the approach I wanted, where button appearances across the entire program could be easily adjusted on the fly, is not really possible without, at the very least, replacing our (probably over a hundred) buttons with custom controls that have custom OS-specific rendering and register themselves with a central authority that can change their colors as needed. I don't really have the scope to implement that kind of change right now, unfortunately. I'll go ahead and mark you as having answered the question though. Thanks again – chris Apr 28 '15 at 15:44
  • @user3421216: I'd add an [L&F menu](http://stackoverflow.com/a/11949899/230513) for testing and recommend an L&F like [Nocturne](http://nocturne.en.softonic.com/mac). – trashgod Apr 28 '15 at 20:16