【问题标题】:Java Nimbus Look and Feel per component customization ("Nimbus.Overrides") - other instances affected too每个组件自定义的 Java Nimbus 外观(“Nimbus.Overrides”)——其他实例也受到影响
【发布时间】:2018-04-13 11:13:58
【问题描述】:

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

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

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

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

  • button 1 - 属性 A(内容边距)已自定义。
  • button 2 - 仅自定义属性 B(字体) - 但属性 A 的变化也可见(更大的 manrgins),“以某种方式继承”自 button 1
  • button 3 - 没有自定义属性(使用空属性映射) - 两个更改 (A+B) 似乎都是从 button1button2 继承的(更大的边距,更大的字体)
  • button 4 - 默认外观 o JButton(无自定义)

    import java.awt.FlowLayout;
    import java.awt.Font;
    import java.awt.Insets;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.UIDefaults;
    
    
    public class NimbusPerComponentTest extends JFrame {
    
    public NimbusPerComponentTest()  {
        super("Test");
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setLayout(new FlowLayout());
    
        // 4 buttons to test
        JButton button1 = new JButton("Button 1");
        JButton button2 = new JButton("Button 2");
        JButton button3 = new JButton("Button 3");
        JButton button4 = new JButton("Button 4");
        add(button1);
        add(button2);
        add(button3);
        add(button4);
        pack();
    
        // style        
        // button 1
        UIDefaults overrides1 = new UIDefaults();
        overrides1.put("Button.contentMargins", new Insets(10,10,10,10));        
        button1.putClientProperty("Nimbus.Overrides", overrides1);
        // button 2
        UIDefaults overrides2 = new UIDefaults();        
        overrides2.put("Button.font", new Font("Sans Serif", Font.BOLD, 15));
        button2.putClientProperty("Nimbus.Overrides", overrides2);
        // button 3
        UIDefaults overrides3 = new UIDefaults();
            // nothing = left empty
        button3.putClientProperty("Nimbus.Overrides", overrides3);
        // button 4
        // no styling        
    
    }
    
    public static void main(String args[]) {        
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(NimbusPerComponentTest.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }       
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new NimbusPerComponentTest().setVisible(true);
            }
        });
    }
    }            
    

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

编辑

在一些答案的启发下:

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

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

编辑 2

我设法找到了一个单行解决方法/hack。看看我自己对我的问题的回答......

【问题讨论】:

    标签: java swing look-and-feel nimbus


    【解决方案1】:

    如果我更改 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 的大小可能会很大。

    【讨论】:

    • 这是一种解决方案,但不是必需的。文档明确指出,覆盖 UIDefaults 应该与外观的默认值合并。
    • 是的,这个答案可能有效(尚未测试),但它取消了仅覆盖某些选定属性并将其与默认属性合并的整个要点。将默认属性的整个映射复制到每个单独的组件必须非常简单......
    • @VGR 是的,但这正是没有按预期/记录的工作,请参阅我的示例
    • 当我写下我的答案时,我认为 Nimbus 中存在一个错误(@VGR 感谢您挖掘源代码),但我仍然觉得我应该提供一个解决方法。这就是我的回答试图描述的。
    【解决方案2】:

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

    在使用putClientProperty("Nimbus.Overrides", overrides) 为每个组件设置样式后,您可以立即防止通过此代码将属性“继承”到随后设置样式的组件:

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

    它有什么作用?

    从功能上讲,什么都没有(空数组 = 什么都没有放),但它会触发 PropertyChange 事件(参见源代码 here),该事件在 NimbusLookAndFeel 类中的 DefaultsListener 私有类中侦听(参见源代码 here),它是我设法找到清除NimbusLookAndFeel 中的compiledDefaults 缓存(?)的唯一方法,在我看来,这是导致问题的原因:

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

    我在NimbusLookAndFeel 类的getDefaultsForPrefix 方法(使用compiledDefaults)中调试时注意到了这个问题 - 请参阅源代码here对于后来的组件,它不仅返回“真正的默认值”,而且出于某种原因还返回了设置为前一个组件的自定义属性。

    明确一点:我是一个完全的业余爱好者,对 Nimbus 课程的细节或架构没有详细的知识或理解。我可能错了,但解决方案对我有用...

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

    一个问题:

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

    【讨论】:

      【解决方案3】:

      我怀疑这是由于 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);
      }
      

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

      【讨论】:

      • 感谢您的提示。可能是我做错了,但是我尝试在我的 NetBeans 中调试此方法,并且在我的示例中,在所有情况下,对于给定的字符串,条件都被评估为真(即正确)(尽管比较代码错误),即出现 clearOverridesCache运行。
      • 看来问题可能出在另一个“缓存”中 - 请参阅我自己对我的问题的回答...
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-26
      • 1970-01-01
      • 2012-09-27
      • 2020-10-12
      相关资源
      最近更新 更多