0

I have a program that I want to be able to switch between "day" and "night" modes, with the "night" mode having 50% gray backgrounds on everything. I'm doing this by calling UIManager.put(key, new color) for all of the "background" keys from this page:

http://alvinalexander.com/java/java-uimanager-color-keys-list

Then I use SwingUtilities.updateComponentTreeUI() in conjunction with java.awt.Window.getWindows() to get existing windows to pick up the change.

When I switch modes, newly-created windows will have the proper background, so the first part works. However, existing windows are behaving oddly: they flash to the new color for a moment, and then switch back. Thus e.g. if I start up the program in Day mode, then the main program window is effectively stuck in Day mode, and vice versa.

I attempted to make an example program. It doesn't behave exactly like my existing program, but it still behaves oddly: the look and feel can only be modified once. After that, new changes are ignored:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Frame;
import javax.swing.JLabel;
import java.awt.GridLayout;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;


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

   public Demo() {
      JFrame frame = new JFrame("Look and feel test");
      frame.setLayout(new GridLayout(3, 3));
      // Fill in non-central locations in the layout.
      // Hacky way to keep the button from monopolizing the frame
      for (int i = 0; i < 4; ++i) {
         frame.add(new JLabel(""));
      }
      JButton button = new JButton("Set new LAF");
      button.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            setNewLAF();
         }
      });
      frame.add(button);
      // Fill in non-central locations in the layout.
      for (int i = 0; i < 4; ++i) {
         frame.add(new JLabel(""));
      }
      frame.setMinimumSize(new Dimension(400, 400));
      frame.show();
      System.out.println("Initial frame background is " + frame.getBackground());
   }

   public void setNewLAF() {
      Random rng = new Random();
      Color newBackground = new Color(rng.nextInt(255),
            rng.nextInt(255), rng.nextInt(255));
      System.out.println("New color is " + newBackground);
      UIManager.put("Panel.background", newBackground);
      for (Frame f : Frame.getFrames()) {
         SwingUtilities.updateComponentTreeUI(f);
         System.out.println("Frame now has background " + f.getBackground());     
      }
   }
}

This produces output such as:

Initial frame background is com.apple.laf.AquaImageFactory$SystemColorProxy[r=238,g=238,b=238]
New color is java.awt.Color[r=243,g=209,b=134]
Frame now has background java.awt.Color[r=243,g=209,b=134]
New color is java.awt.Color[r=205,g=141,b=58]
Frame now has background java.awt.Color[r=243,g=209,b=134]
New color is java.awt.Color[r=141,g=22,b=92]
Frame now has background java.awt.Color[r=243,g=209,b=134]

Thanks for any advice you'd care to share.

chris
  • 171
  • 10
  • 3
    You will need to call `updateUI` on at least the top level/root container if not all the UI elements. Generally speaking, the look and feel isn't really designed for this – MadProgrammer Jan 13 '15 at 22:57
  • Do you have a recommended approach for switching the background color of not just every frame but all of the components in the frames, at runtime? Look and feel is the closest option I can see short of manually walking the component hierarchy and calling setBackground() on every single element. – chris Jan 14 '15 at 15:47

2 Answers2

1

it still behaves oddly: the look and feel can only be modified once.

Color newBackground = new Color(rng.nextInt(255),rng.nextInt(255), rng.nextInt(255));

You need to use:

Color newBackground = new ColorUIResource(rng.nextInt(255),rng.nextInt(255), rng.nextInt(255));

The updateComponentTreeUI() only replaces resources that are part of the UI. You do this by wrapping the Color in a ColorUIResource.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Aha, thanks! That fixes the demo program, and substantially improves the actual use case -- now all of the windows except for the one that actually handles the day/night switching logic are updating properly. I think at this point I just have an error in my order of operations. Thank you! – chris Jan 14 '15 at 15:45
0

Why don't you replace your UIManager based code with something like this?

public void setNewLAF(JFrame frame) {
    Random rng = new Random();
    Color newBackground = new Color(rng.nextInt(255), rng.nextInt(255),
            rng.nextInt(255));
    System.out.println("New color is " + newBackground);
    frame.setBackground(newBackground);
    frame.getContentPane().setBackground(newBackground);
}

It might not work well with your case (e.g., if you have many frames) but I think that you can probably just dump your frames into an ArrayList<JFrame> and iterate over them.

Also, just to let you know: show() is deprecated. Use setVisible(true).

k_g
  • 4,333
  • 2
  • 25
  • 40
  • Sorry, I should have mentioned this in the original question -- if you only set the frame background, then the backgrounds of other elements (like buttons, comboboxes, etc.) are still white, and it looks awful. That's why I go through that list of UIManager keys and update each of them. – chris Jan 14 '15 at 15:39
  • Oh, and also, I believe that on Windows if you try to use `setBackground` in this manner, then it just flat-out doesn't work; the PLAF overrides your background color with the appropriate system one. Or at least that's part of the developer lore here; I haven't personally tested it. Thanks for the reminder about `show()` though. – chris Jan 14 '15 at 15:52
  • I am using Windows 8 and it works. So it might be lore, or it might just apply to versions before 8. – k_g Jan 14 '15 at 21:55