How do I force all Components in a JFrame to reacquire their Background property for light/dark mode purposes?

huangapple go评论83阅读模式
英文:

How do I force all Components in a JFrame to reacquire their Background property for light/dark mode purposes?

问题

我正在尝试编写一个应用程序,为了用户体验,我希望界面具有浅色模式和深色模式...因此,我有两个颜色变量,并且有一个getForeground()和getBackground()方法,根据我的DarkMode布尔值的设置返回适当的颜色。我想要的是找到一种方法,使窗口中的现有组件重新初始化其设置,即重置背景和前景,而无需销毁并重新打开窗口。我的按钮可以切换标志,点击后打开并绘制的新面板在浅色模式和深色模式之间正确切换,但我无法弄清如何使不经常更新的面板重新绘制自己,而不会丢失其当前内容。

我尝试过使用

frame.invalidate();
frame.validate();
frame.repaint();

我也尝试过

frame.setVisible(false);
frame.setVisible(true);

以及在JPanels上尝试了这两个选项,但都没有改变现有的颜色调色板,除非切换到不同的面板。我觉得肯定有一些简单的东西我漏掉了。

JPanel navPanel = new JPanel();//左侧应用程序的导航面板
/*
 * 用于应用程序的广泛概念区域的按钮,始终固定在左侧
 */
NavButton employeeButton = new NavButton("Employees", Main.getEmployeesIcon());
NavButton settingsButton = new NavButton("Settings", Main.getSettingsIcon());
NavButton scheduleButton = new NavButton("Schedules", Main.getScheduleIcon());
NavButton saveButton = new NavButton("Save Data", Main.getSaveIcon());

navPanel.setBackground(Main.background());
navPanel.setOpaque(true);
navPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
navPanel.setPreferredSize(new Dimension(180,180));
navPanel.add(employeeButton);
navPanel.add(scheduleButton);
navPanel.add(saveButton);
navPanel.add(settingsButton);
navPanel.add(new NavButton("Mode Change", Main.getSaveIcon()) {
    {
        addActionListener(ae ->{
            Main.toggleDarkMode();
            SwingUtilities.updateComponentTreeUI(mainWindow);
        });
    }
});
英文:

I am trying to write an application which, for UX purposes, I want the UI to have a light mode and a dark mode... as a result, I have two color variables, and I have a getForeground() and a getBackground() that returns the appropriate colors, based on the setting of my boolean DarkMode. What I would like is to find a way to make the existing components in the window re-initialize their settings, IE: reset Background and Foreground, without having to dispose and reopen the frame. My button works to toggle the flag, and new panels opened and drawn after it's clicked are properly toggled between light and dark mode, but I can't figure out how to make the less-often-updated panels redraw themselves without loosing their current contents.

I have tried using
frame.invalidate();
frame.validate();
frame.repaint();

I have also tried
frame.setVisible(false);
frame.setVisible(true);

and both those options on the JPanels as well, nothing changes the existing color palette save moving to a different panel. I feel like there must be something simple I'm missing.

JPanel navPanel = new JPanel();//Navigation panel for the left side of the app
	/*
	 * buttons for broad concept areas of the application to be always pinned at the left
	 */
	NavButton employeeButton = new NavButton("Employees", Main.getEmployeesIcon());
	NavButton settingsButton = new NavButton("Settings", Main.getSettingsIcon());
	NavButton scheduleButton = new NavButton("Schedules", Main.getScheduleIcon());
	NavButton saveButton = new NavButton("Save Data", Main.getSaveIcon());
	
	navPanel.setBackground(Main.background());
	navPanel.setOpaque(true);
	navPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
	navPanel.setPreferredSize(new Dimension(180,180));
	navPanel.add(employeeButton);
	navPanel.add(scheduleButton);
	navPanel.add(saveButton);
	navPanel.add(settingsButton);
	navPanel.add(new NavButton("Mode Change", Main.getSaveIcon()) {
		{
			addActionListener(ae ->{
				Main.toggleDarkMode();
				SwingUtilities.updateComponentTreeUI(mainWindow);
			});
		}
	});

答案1

得分: 1

根据Oracle的说法:

在启动后更改外观

即使程序的GUI已可见,您也可以使用setLookAndFeel更改外观。要使现有组件反映新的外观,请对每个顶级容器调用SwingUtilities.updateComponentTreeUI方法一次。然后,您可能希望调整每个顶级容器的大小,以反映其包含的组件的新大小。例如:

UIManager.setLookAndFeel(lnfName);
SwingUtilities.updateComponentTreeUI(frame);
frame.pack();

https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html#dynamic

英文:

According to Oracle it's:

Changing the Look and Feel After Startup

You can change the L&F with setLookAndFeel even after the program's GUI is visible. To make existing components reflect the new L&F, invoke the SwingUtilities updateComponentTreeUI method once per top-level container. Then you might wish to resize each top-level container to reflect the new sizes of its contained components. For example:

UIManager.setLookAndFeel(lnfName);
SwingUtilities.updateComponentTreeUI(frame);
frame.pack();

https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html#dynamic

答案2

得分: 0

切换外观在运行时是一种偶然的副作用,API实际上从未被设计为真正支持它,只是要注意这可能会以意想不到的方式中断。

"真正"的答案是从一开始就为其设计。也就是说,设计一个真正支持浅色和深色模式的外观,或者设计两种可以在之间切换的外观。这允许您定义每个组件应该使用的"默认"值,并使在它们之间切换变得更容易。

我的意思是,你要问自己这个问题,如果背景颜色是以编程方式定义的,你应该更改它吗?

"另一种"解决方案是修改当前外观使用的默认值。这在某种程度上有些问题,因为并非所有外观都使用相同的键集来定义它们的默认值,甚至别让我开始谈论 JButton

以下是这个想法的一个过度简化概念。当我说"简化"时,我不是在开玩笑。UIManager 使用了大约 719 个键,并且我还没有开始查看诸如 Border 属性之类的东西。

英文:

Switching the look & feel at run time is a fluke side effect and the API was never designed to actually support it, just beware that this could break in unexpected ways.

The "real" answer is to design for it from the start. That is, design an actual look and feel which supports light and dark mode (or design two which can be switched between). This allows you to define the "default" values every component should use and makes it significantly easier to switch between them.

I mean, as yourself the question, if the background color has been defined programmatically, should you be changing it?

"Another" solution would be to modify the default values used by the current look and feel. This is some what problematic as not all look and feels use the same key sets to define there default values and don't even get me started on JButton.

The following is an overly simplified concept of the idea. When I say "simplified", I'm not kidding. There were some 719 key's been used by the UIManager and I didn't start looking at things like the Border properties.

How do I force all Components in a JFrame to reacquire their Background property for light/dark mode purposes?

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ColorUIResource;

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setBorder(new EmptyBorder(32, 32, 32, 32));
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;

            add(new JLabel("Hello"), gbc);
            add(new JTextField("Some text", 20), gbc);

            JButton flip = new JButton("Flip");
            add(flip, gbc);

            flip.addActionListener(new ActionListener() {
                private boolean isDark = false;

                @Override
                public void actionPerformed(ActionEvent e) {
                    isDark = !isDark;
                    System.out.println("IsDark " + isDark);
                    // It would be better to store the "default" values at launch,
                    // to make it easier to switch back to
                    UIManager.put("Panel.background", isDark ? new ColorUIResource(Color.BLACK) : new ColorUIResource(new Color(238, 238, 238)));

                    UIManager.put("Label.foreground", isDark ? new ColorUIResource(Color.WHITE) : new ColorUIResource(Color.BLACK));
                    UIManager.put("Label.disabledForeground", isDark ? new ColorUIResource(new Color(192, 192, 192)) : new ColorUIResource(Color.BLACK));
                    UIManager.put("Label.background", isDark ? new ColorUIResource(Color.BLACK) : new ColorUIResource(new Color(238, 238, 238)));

                    UIManager.put("TextField.background", isDark ? new ColorUIResource(new Color(64, 64, 64)) : new ColorUIResource(Color.WHITE));
                    UIManager.put("TextField.foreground", isDark ? new ColorUIResource(Color.WHITE) : new ColorUIResource(Color.BLACK));

                    SwingUtilities.updateComponentTreeUI(TestPane.this);

                    JRootPane rootPane = SwingUtilities.getRootPane(TestPane.this);
                    if (rootPane == null) {
                        return;
                    }
                    rootPane.updateUI();
                }
            });
        }
    }
}

You might also consider having a look around and seeing what else is available.

For example...

huangapple
  • 本文由 发表于 2023年7月7日 06:59:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76632991.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定