Java 每个组件定制的 Nimbus 外观和感觉 ("Nimbus.Overrides") - 其他实例也受到影响

Java Nimbus Look and Feel per component customization ("Nimbus.Overrides") - other instances affected too

我是Java Nimbus 外观 的新手。我正在尝试使用 Nimbus 的功能 自定义单个组件实例 使用 putClientProperty("Nimbus.Overrides", overrides): https://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/nimbus/package-summary.html

我遇到了以下问题(错误?):

不幸的是,

"Nimbus.Overrides" 值显然不仅会影响明确设置的组件对象,还会影响其他对象。

似乎自定义属性 "inherited" 以某种方式与其他(后来 "styled",显然不是以前)相同类型的实例。我需要确保只对一个单独的实例进行更改(不影响任何其他对象)。示例 - 使用了 JButton,但遇到了同样的问题,例如JTabbedPane 和自定义画家:

知道为什么吗?我错过了什么?任何优雅的解决方法? (Java 8, Windows 10)

编辑

经过一些回答的启发:

尝试在我的原始代码末尾重置外观(为 null 并再次返回 Nimbus,包括 SwingUtilities.updateComponentTreeUI),唯一的结果:

现在即使是 button4 也画错了(边距和字体都改变了),尽管通用默认值从未被触及过......很奇怪。

编辑 2

我设法找到了 单行解决方法 /hack。请参阅我自己对我的问题的回答...

如果我改变 UIDefaults 的创建,它对我有用。而不是

UIDefaults overrides1 = new UIDefaults();

我用过

UIDefaults overrides1 = (UIDefaults) UIManager.getLookAndFeelDefaults().clone();

overrides2overrides3 也这样做:

    // style
    // button 1
    UIDefaults overrides1 = (UIDefaults) UIManager.getLookAndFeelDefaults().clone();
    overrides1.put("Button.contentMargins", new Insets(10, 10, 10, 10));
    button1.putClientProperty("Nimbus.Overrides", overrides1);
    // button 2
    UIDefaults overrides2 = (UIDefaults) UIManager.getLookAndFeelDefaults().clone();
    overrides2.put("Button.font", new Font("Sans Serif", Font.BOLD, 15));
    button2.putClientProperty("Nimbus.Overrides", overrides2);
    // button 3
    UIDefaults overrides3 = (UIDefaults) UIManager.getLookAndFeelDefaults().clone();
    // nothing = left empty
    button3.putClientProperty("Nimbus.Overrides", overrides3);
    // button 4
    // no styling

这会在我的机器上产生以下输出:

请注意,以这种方式创建的 UIDefaults 大小可能会很大。

我怀疑这是由于 javax.swing.plaf.nimbus.NimbusLookAndFeel 的 shouldUpdateStyleOnEvent 方法中的错误所致。来自 the source:

protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) {
    String eName = ev.getPropertyName();

    // These properties affect style cached inside NimbusDefaults (6860433)
    if ("name" == eName ||
        "ancestor" == eName ||
        "Nimbus.Overrides" == eName ||
        "Nimbus.Overrides.InheritDefaults" == eName ||
        "JComponent.sizeVariant" == eName) {

        JComponent c = (JComponent) ev.getSource();
        defaults.clearOverridesCache(c);
        return true;
    }

    return super.shouldUpdateStyleOnEvent(ev);
}

不用说,这不是比较字符串的有效方法。我无法在错误数据库中找到任何关于此的信息;也许以后有时间,我会提交错误报告。

经过相当长时间的调试,我似乎找到了一个单行解决方法/hack,这也可能暗示了问题的原因:

在使用 putClientProperty("Nimbus.Overrides", overrides) 设置每个组件的样式后,您可以立即通过以下代码阻止 "inheriting" 属性随后设置样式的组件:

button1.putClientProperty("Nimbus.Overrides", overrides1);
UIManager.getDefaults().putDefaults(new Object[0]); 
  // add after each "styling"
  // - clears the compiledDefaults in NimbusLookAndFeel

它有什么作用?

在功能上,什么都没有(empy array = nothing put),但它触发了 PropertyChange 事件(参见来源 here), which is listened in DefaultsListener private class in NimbusLookAndFeel class (see source here),这是我设法找到 清除 compiledDefaults NimbusLookAndFeel 中的缓存 (?),这在我看来是导致问题的原因:

if ("UIDefaults".equals(key)) {
    compiledDefaults = null;
}

我在 getDefaultsForPrefix NimbusLookAndFeel class 中调试 getDefaultsForPrefix 方法(使用 compiledDefaults)时注意到了这个问题 - 请参阅来源 here对于后来的组件,它不仅返回 "real defaults",而且出于某种原因还返回设置为先前组件的自定义属性。

澄清一下:我完全是个业余爱好者,对 Nimbus classes 的细节或架构没有详细的知识或了解。我可能是错的,但解决方案对我有用...

是否因为某些原因不适合?有什么风险吗?真的是bug吗?

一道题:

我注意到如果我 pack() 我的框架 之前 使用 putClientProperty 为组件设置样式(如我问题中的代码),但是 之后,解决方案不起作用(没有任何帮助)...