通过交换 UIDefaults 改变外观
Changing Look And Feel by Swapping UIDefaults
我正在编写一个 GUI 构建器,并希望用户能够更改他构建的 GUI 的 LookAndFeel。 LookAndFeel 只应针对编辑器区域内的组件进行更改。应用程序的其余部分应保留在 SystemLookAndFeel 中。
最大的问题是,LookAndFeel 是作为单例实现的,并且在应用程序期间多次更改 LookAndFeel 会导致很多错误。
我开始尝试使用按钮:
我尝试将 ButtonUI 设置为 MetalButtonUI,但它们没有正确呈现。所以我调试了 JButton 的默认 paintComponent 方法,发现 ButtonUI 仍然需要 UIDefaults,这并不完整,因为它们是 WindowsUIDefaults。
我目前的解决方案是设置 MetalLookAndFeel,保存 UIDefaults,然后将 LookAndFeel 更改为 SystemLookAndFeel 并保存这些 UIDefaults,每次我在编辑器中绘制 Button 时我都会交换 UIDefaults。
这是代码:
public class MainClass{
public static Hashtable systemUI;
public static Hashtable metalUI;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
metalUI = new Hashtable();
metalUI.putAll(UIManager.getDefaults());
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
systemUI = new Hashtable();
systemUI.putAll(UIManager.getDefaults());
} catch (Exception e) {
e.printStackTrace();
}
/* ...
* Generate JFrame and other stuff
* ...
*/
}
});
}
}
public class MyButton extends JButton {
public MyButton(String text) {
super(text);
ui = MetalButtonUI.createUI(this);
}
@Override public synchronized void paintComponent(Graphics g) {
UIManager.getDefaults().putAll(Application.metalUI);
super.paintComponent(g);
UIManager.getDefaults().putAll(Application.systemUI);
}
}
如您所见here 结果非常好。左边是 MetalLaF 的外观,右边是它在我的应用程序中的呈现方式。渐变绘制正确,但边框和字体不正确。
所以我需要知道为什么不是 LaF 的所有元素都被应用到 Button 以及如何解决这个问题。
-
编辑:
我找到了一个 丑陋 的解决方案。 LookAndFeel 必须在 Button 创建之前更改,因为 Graphics 对象将在 Constructor 中创建。调用超级构造函数后,您可以将 LookAndFeel 改回来。
接下来需要在Componentpainted/repainted之前更改LookAndFeel。我让它工作的唯一一点是在调用 super 之前在 paintComponent 中。调用super后可以改回来
代码:
import javax.swing.*;
import javax.swing.plaf.metal.MetalButtonUI;
import java.awt.*;
public class MainClass {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(f.getExtendedState() | JFrame.EXIT_ON_CLOSE);
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception ex) {
}
f.setLayout(new FlowLayout());
f.add(new MyButton("MetalButton"));
f.add(new JButton("SystemButton"));
f.pack();
f.setVisible(true);
}
});
}
}
class MyButton extends JButton {
public MyButton(String text) {
super(text);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
ui = MetalButtonUI.createUI(this);
}
@Override public synchronized void paintComponent(Graphics g) {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
super.paintComponent(g);
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
}
}
编辑 2:
除非绝对必要,否则不要使用它!
这非常不稳定。经过大量测试后,我发现当我只是交换 UIDefaults 而不是整个 LookAndFeel 时,错误更少,但我不建议做任何这些。
编辑 3:
我找到的最佳解决方案是使用 JavaFX 作为 GUI。我将一个 swing 节点插入到编辑器区域,现在可以根据需要随意修改 swing 组件的外观,而不会产生任何明显的副作用。
咆哮:
如果您想修改应用程序的样式,则始终可以选择 JavaFX。 CSS 尽可能简单,没有任何副作用!
非常感谢
强尼
免责声明
Swing 的 Look And Feel 并非设计为在首次初始化后进行切换,它实际上是一种可能发生的偶然副作用。某些外观和感觉以及某些组件可能不喜欢您这样做,并且可能不会像正常情况下那样表现。
可能的解决方案
为了理智起见,请勿更改 paintComponent 方法中的 UI 默认值(根本不要从任何 paint
中更改 UI 的状态方法 EVER,绘画仅绘制当前状态),这只是自找麻烦。
相反,在需要时使用 UIManager.setLookAndFeel(,,,)
和 SwingUtiltiies#updateComponentTreeUI
并传入最顶层容器
例如...
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LookAndFeelSwitcher {
public static void main(String[] args) {
new LookAndFeelSwitcher();
}
public LookAndFeelSwitcher() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(2, 2, 2, 2);
add(new JLabel("I have a bad feeling about this"), gbc);
add(new JTextField("When this blows up in your face, don't blame me"), gbc);
UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
DefaultComboBoxModel model = new DefaultComboBoxModel(lafs);
JComboBox cb = new JComboBox(model);
cb.setRenderer(new LookAndFeelInfoListCellRenderer());
add(cb, gbc);
String name = UIManager.getLookAndFeel().getName();
for (int index = 0; index < model.getSize(); index++) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) model.getElementAt(index);
if (info.getName().equals(name)) {
model.setSelectedItem(info);
break;
}
}
cb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) cb.getSelectedItem();
String className = info.getClassName();
try {
UIManager.setLookAndFeel(className);
SwingUtilities.updateComponentTreeUI(SwingUtilities.windowForComponent(TestPane.this));
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
}
});
}
public class LookAndFeelInfoListCellRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value instanceof UIManager.LookAndFeelInfo) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) value;
value = info.getName();
}
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
}
}
}
我正在编写一个 GUI 构建器,并希望用户能够更改他构建的 GUI 的 LookAndFeel。 LookAndFeel 只应针对编辑器区域内的组件进行更改。应用程序的其余部分应保留在 SystemLookAndFeel 中。
最大的问题是,LookAndFeel 是作为单例实现的,并且在应用程序期间多次更改 LookAndFeel 会导致很多错误。
我开始尝试使用按钮: 我尝试将 ButtonUI 设置为 MetalButtonUI,但它们没有正确呈现。所以我调试了 JButton 的默认 paintComponent 方法,发现 ButtonUI 仍然需要 UIDefaults,这并不完整,因为它们是 WindowsUIDefaults。
我目前的解决方案是设置 MetalLookAndFeel,保存 UIDefaults,然后将 LookAndFeel 更改为 SystemLookAndFeel 并保存这些 UIDefaults,每次我在编辑器中绘制 Button 时我都会交换 UIDefaults。
这是代码:
public class MainClass{
public static Hashtable systemUI;
public static Hashtable metalUI;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
metalUI = new Hashtable();
metalUI.putAll(UIManager.getDefaults());
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
systemUI = new Hashtable();
systemUI.putAll(UIManager.getDefaults());
} catch (Exception e) {
e.printStackTrace();
}
/* ...
* Generate JFrame and other stuff
* ...
*/
}
});
}
}
public class MyButton extends JButton {
public MyButton(String text) {
super(text);
ui = MetalButtonUI.createUI(this);
}
@Override public synchronized void paintComponent(Graphics g) {
UIManager.getDefaults().putAll(Application.metalUI);
super.paintComponent(g);
UIManager.getDefaults().putAll(Application.systemUI);
}
}
如您所见here 结果非常好。左边是 MetalLaF 的外观,右边是它在我的应用程序中的呈现方式。渐变绘制正确,但边框和字体不正确。
所以我需要知道为什么不是 LaF 的所有元素都被应用到 Button 以及如何解决这个问题。
-
编辑: 我找到了一个 丑陋 的解决方案。 LookAndFeel 必须在 Button 创建之前更改,因为 Graphics 对象将在 Constructor 中创建。调用超级构造函数后,您可以将 LookAndFeel 改回来。
接下来需要在Componentpainted/repainted之前更改LookAndFeel。我让它工作的唯一一点是在调用 super 之前在 paintComponent 中。调用super后可以改回来
代码:
import javax.swing.*;
import javax.swing.plaf.metal.MetalButtonUI;
import java.awt.*;
public class MainClass {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(f.getExtendedState() | JFrame.EXIT_ON_CLOSE);
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception ex) {
}
f.setLayout(new FlowLayout());
f.add(new MyButton("MetalButton"));
f.add(new JButton("SystemButton"));
f.pack();
f.setVisible(true);
}
});
}
}
class MyButton extends JButton {
public MyButton(String text) {
super(text);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
ui = MetalButtonUI.createUI(this);
}
@Override public synchronized void paintComponent(Graphics g) {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
super.paintComponent(g);
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
}
}
编辑 2: 除非绝对必要,否则不要使用它!
这非常不稳定。经过大量测试后,我发现当我只是交换 UIDefaults 而不是整个 LookAndFeel 时,错误更少,但我不建议做任何这些。
编辑 3: 我找到的最佳解决方案是使用 JavaFX 作为 GUI。我将一个 swing 节点插入到编辑器区域,现在可以根据需要随意修改 swing 组件的外观,而不会产生任何明显的副作用。
咆哮: 如果您想修改应用程序的样式,则始终可以选择 JavaFX。 CSS 尽可能简单,没有任何副作用!
非常感谢
强尼
免责声明
Swing 的 Look And Feel 并非设计为在首次初始化后进行切换,它实际上是一种可能发生的偶然副作用。某些外观和感觉以及某些组件可能不喜欢您这样做,并且可能不会像正常情况下那样表现。
可能的解决方案
为了理智起见,请勿更改 paintComponent 方法中的 UI 默认值(根本不要从任何 paint
中更改 UI 的状态方法 EVER,绘画仅绘制当前状态),这只是自找麻烦。
相反,在需要时使用 UIManager.setLookAndFeel(,,,)
和 SwingUtiltiies#updateComponentTreeUI
并传入最顶层容器
例如...
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LookAndFeelSwitcher {
public static void main(String[] args) {
new LookAndFeelSwitcher();
}
public LookAndFeelSwitcher() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(2, 2, 2, 2);
add(new JLabel("I have a bad feeling about this"), gbc);
add(new JTextField("When this blows up in your face, don't blame me"), gbc);
UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
DefaultComboBoxModel model = new DefaultComboBoxModel(lafs);
JComboBox cb = new JComboBox(model);
cb.setRenderer(new LookAndFeelInfoListCellRenderer());
add(cb, gbc);
String name = UIManager.getLookAndFeel().getName();
for (int index = 0; index < model.getSize(); index++) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) model.getElementAt(index);
if (info.getName().equals(name)) {
model.setSelectedItem(info);
break;
}
}
cb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) cb.getSelectedItem();
String className = info.getClassName();
try {
UIManager.setLookAndFeel(className);
SwingUtilities.updateComponentTreeUI(SwingUtilities.windowForComponent(TestPane.this));
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
}
});
}
public class LookAndFeelInfoListCellRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value instanceof UIManager.LookAndFeelInfo) {
UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) value;
value = info.getName();
}
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
}
}
}