Java: 系统 L&F 仅第二次正确显示

Java: system L&F only appearing correctly for second time

我正在使用这段代码来设置程序的外观: UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName()); 我只在程序运行时显示的 JPanel(在 JFrame 内)上使用系统外观。我第一次显示 JPanel 时,L&F 是错误的(看起来它是针对旧版本的 windows 或其他内容,与我之前在其他组件上使用的不同)。我隐藏 JPanel 然后再次打开它,L&F 现在是正确的。 可悲的是,我无法生成可重现的示例,但问题仍然存在于原始程序中。 它由多个类组成,并且有一个面向对象编写的UI,这是负责有问题的JPanel的代码:

package almanah;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class SeriesProperties extends JPanel {

    JButton button_close = new JButton();
    JPanel container = new JPanel();
    JScrollPane scPane = new JScrollPane(container);

    public SeriesProperties(ItemLib lib) {
        
        try {
            UIManager.setLookAndFeel(
            UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(SeriesProperties.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(SeriesProperties.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(SeriesProperties.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedLookAndFeelException ex) {
            Logger.getLogger(SeriesProperties.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        this.setLayout(new BorderLayout());

        this.add(button_close, BorderLayout.EAST);
        button_close.addActionListener((e) -> {
            Almanah.frame.swapToTiles();
        });

        scPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        scPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        this.add(scPane, BorderLayout.CENTER);
        
        //container
        container.setBackground(Color.red);
        container.setLayout(new WrapLayout());
        
        for (int i = 0; i < 100; i++) {
            JPanel panel=new JPanel();
            panel.setPreferredSize(new Dimension(100, 100));
            container.add(panel);
        }
        this.updateUI();
    }

}

在创建任何 UI 元素之前先确定外观状态。在运行时切换 L&F 的能力实际上是一种副作用,通常不鼓励这样做,因为它从来都不是系统的一个特性。

相反,可能在您的 main 方法中,设置外观,然后在其后构建您的 UI。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

    public Test() {
        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();
                // I don't know what ItemLib, as the source is incomplete
                frame.add(new SeriesProperties(...));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class SeriesProperties extends JPanel {

        JButton button_close = new JButton();
        JPanel container = new JPanel();
        JScrollPane scPane = new JScrollPane(container);

        public SeriesProperties(ItemLib lib) {

            this.setLayout(new BorderLayout());

            this.add(button_close, BorderLayout.EAST);
            button_close.addActionListener((e) -> {
                // This seems like a bad idea
                // You should consider using a observer/delegate
                // pattern instead, reducing the coupling of your code
                Almanah.frame.swapToTiles();
            });

            scPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
            scPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
            this.add(scPane, BorderLayout.CENTER);

            //container
            container.setBackground(Color.red);
            container.setLayout(new WrapLayout());

            for (int i = 0; i < 100; i++) {
                JPanel panel = new JPanel();
                panel.setPreferredSize(new Dimension(100, 100));
                container.add(panel);
            }
            //this.updateUI();
        }

    }
}

解决方案:

Error13660,你的问题解决了吗?这是我改变外观的解决方案。

并查看 This answer

package snippet;

import java.awt.EventQueue;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(() -> {
            
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                    | UnsupportedLookAndFeelException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            var frame = new JFrame();
            
            var panel = new JPanel();
            
            { // Button 0: onclick change theme 0
                var btn = new JButton("Theme 0");
                btn.addActionListener((e) -> {
                    System.out.println("actionPerformed: change theme 0");
                    Test.changeLaF(frame, "javax.swing.plaf.metal.MetalLookAndFeel");
                });
                panel.add(btn);
            }
         
            { // Button 1: onclick change theme 1
                var btn = new JButton("Theme 1");
                btn.addActionListener((e) -> {
                    System.out.println("actionPerformed: change theme 1");
                    Test.changeLaF(frame, "com.sun.java.swing.plaf.motif.MotifLookAndFeel");
                });
                panel.add(btn);
            } 
            
            frame.add(panel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
 
    
    public static final void changeLaF(final JFrame frame, final String nameLookAndFeel) {
        try {
            UIManager.setLookAndFeel(nameLookAndFeel);
            SwingUtilities.updateComponentTreeUI(frame);
            frame.pack ();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                | UnsupportedLookAndFeelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

24-02-21 之前的版本:

您尝试过使用SwingUtilities.updateComponentTreeUI(frame);

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:

参见:lookandfeel/plaf#Dynamic

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

更新: /!\ 如果成功加载 L&F,所有组件都使用该 L&Flookandfeel/plaf#Steps