Question

I'm writing a script for a larger GUI application. The main application window uses the system's LookAndFeel, but I want my script's GUI to use the Nimbus LookAndFeel. After GUI creation, I want to set the LookAndFeel back to the original. I feel the below SSCCE should work, but I'm getting a NullPointerException when using my Component objects.

import java.awt.Dimension;
import java.awt.GridBagLayout;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;

public class GUI extends JFrame {
    private static LookAndFeel originalLookAndFeel = UIManager.getLookAndFeel();
    static {
        System.out.println("At start, look and feel is " + UIManager.getLookAndFeel().getName());
        try {
            setNimbusLookAndFeel();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("Look and feel changed to " + UIManager.getLookAndFeel().getName()
                + " before component creation");
    }
    private GridBagLayout gridBag = new GridBagLayout();
    private JTabbedPane tabs = new JTabbedPane();
    private JPanel selectionPanel = new JPanel(gridBag);
    private JPanel infoPanel = new JPanel(gridBag);
    private JPanel settingsPanel = new JPanel(gridBag);

    public GUI() {
        setWindowProperties();
        setUpComponents();
        addComponents();

        try {
            System.out.println("Setting to original, which is " + originalLookAndFeel.getName());
            UIManager.setLookAndFeel(originalLookAndFeel);
            System.out.println("Current look and feel is " + UIManager.getLookAndFeel().getName());
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }

    private void setWindowProperties() {
        setLayout(gridBag);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(new Dimension(700, 600));
        setTitle("fAmos Quester");
        setResizable(false);
        setLocationRelativeTo(null);
    }

    private static void setNimbusLookAndFeel() {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                }
            }
        } catch (Exception e) {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception e2) {
            }
        }
    }

    public void setUpComponents() {
        tabs.addTab("Quest selection", selectionPanel);
        tabs.addTab("Quest info", infoPanel);
        tabs.addTab("Settings", settingsPanel);

        selectionPanel.setPreferredSize(new Dimension(650, 500));
        infoPanel.setPreferredSize(new Dimension(650, 500));
        settingsPanel.setPreferredSize(new Dimension(650, 500));
    }

    private void addComponents() {
        add(tabs);
    }

    public static void main(String[] args) {
        new GUI().setVisible(true);
    }
}
Was it helpful?

Solution

As a general rule it is not a good idea to mix LAF's. This problem is an example of why.

There is something in the Nimbus LAF that may not allow you to do this. Run the code as is. It will set the LAF to the System LAF and then reset the LAF. In my case the system is Windows and it appear to work fine. Then change the code to use the Nimbus LAF. It appears to work initially, but try resizing the frame and you get the errors. So it would appear that the Nimbus frame does not work completely independent of the current LAF.

import java.awt.*;
import java.awt.event.*;
import java.awt.GridBagLayout;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;

public class GUI2 extends JFrame {
    private static LookAndFeel originalLookAndFeel = UIManager.getLookAndFeel();
/*
    private GridBagLayout gridBag = new GridBagLayout();
    private JTabbedPane tabs = new JTabbedPane();
    private JPanel selectionPanel = new JPanel(gridBag);
    private JPanel infoPanel = new JPanel(gridBag);
    private JPanel settingsPanel = new JPanel(gridBag);
*/
    private GridBagLayout gridBag;
    private JTabbedPane tabs;
    private JPanel selectionPanel;
    private JPanel infoPanel;
    private JPanel settingsPanel;

    public GUI2() {
        System.out.println("At start, look and feel is " + UIManager.getLookAndFeel().getName());
        try {
//            setNimbusLookAndFeel();
          UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Look and feel changed to " + UIManager.getLookAndFeel().getName()
                + " before component creation");

        gridBag = new GridBagLayout();
        setLayout(gridBag);
        tabs = new JTabbedPane();
        selectionPanel = new JPanel(gridBag);
        infoPanel = new JPanel(gridBag);
        settingsPanel = new JPanel(gridBag);

        setUpComponents();
        addComponents();
        setWindowProperties();

        Action reset = new AbstractAction()
        {
            public void actionPerformed(ActionEvent ae)
            {
                try {
                    System.out.println("Setting to original, which is " + originalLookAndFeel.getName());
                    UIManager.setLookAndFeel(originalLookAndFeel);
                    System.out.println("Current look and feel is " + UIManager.getLookAndFeel().getName());
                } catch (UnsupportedLookAndFeelException e) {
                    //e.printStackTrace();
                    System.out.println(e.getMessage());
                }

            }
        };

        Timer timer = new Timer(500, reset);
        timer.setRepeats(false);
        timer.start();
    }

    private void setWindowProperties() {
//        setLayout(gridBag);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle("fAmos Quester");
//        setResizable(false);
        pack();
        setLocationRelativeTo(null);
    }

    private void setNimbusLookAndFeel() {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                }
            }
        } catch (Exception e) {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception e2) {
            }
        }
    }

    public void setUpComponents() {
        tabs.addTab("Quest selection", selectionPanel);
        tabs.addTab("Quest info", infoPanel);
        tabs.addTab("Settings", settingsPanel);

        selectionPanel.setPreferredSize(new Dimension(650, 500));
        infoPanel.setPreferredSize(new Dimension(650, 500));
        settingsPanel.setPreferredSize(new Dimension(650, 500));
    }

    private void addComponents() {
        add(tabs);
    }

    public static void main(String[] args) {
        new GUI2().setVisible(true);
    }
}

Maybe a solution would be to create the component using the Nimbus LAF as is done above. However, don't reset the LAF until the frame is deactivated. Then you could try resetting the LAF every time the frame is activated. You would use a WindowListener to handle the activated/deactivated events.

OTHER TIPS

The problem originates from attempting to do the PLAF change in a static block. Move it to the constructor and it works.

import java.awt.Dimension;
import java.awt.GridBagLayout;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;

public class GUI extends JFrame {
    private static LookAndFeel originalLookAndFeel = UIManager.getLookAndFeel();
    private GridBagLayout gridBag = new GridBagLayout();
    private JTabbedPane tabs = new JTabbedPane();
    private JPanel selectionPanel = new JPanel(gridBag);
    private JPanel infoPanel = new JPanel(gridBag);
    private JPanel settingsPanel = new JPanel(gridBag);

    public GUI() {
        System.out.println("At start, look and feel is " + UIManager.getLookAndFeel().getName());
        try {
            setNimbusLookAndFeel();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Look and feel changed to " + UIManager.getLookAndFeel().getName()
                + " before component creation");

        setWindowProperties();
        setUpComponents();
        addComponents();

        try {
            System.out.println("Setting to original, which is " + originalLookAndFeel.getName());
            UIManager.setLookAndFeel(originalLookAndFeel);
            System.out.println("Current look and feel is " + UIManager.getLookAndFeel().getName());
        } catch (UnsupportedLookAndFeelException e) {
            //e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }

    private void setWindowProperties() {
        setLayout(gridBag);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(new Dimension(700, 600));
        setTitle("fAmos Quester");
        setResizable(false);
        setLocationRelativeTo(null);
    }

    private void setNimbusLookAndFeel() {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                }
            }
        } catch (Exception e) {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception e2) {
            }
        }
    }

    public void setUpComponents() {
        tabs.addTab("Quest selection", selectionPanel);
        tabs.addTab("Quest info", infoPanel);
        tabs.addTab("Settings", settingsPanel);

        selectionPanel.setPreferredSize(new Dimension(650, 500));
        infoPanel.setPreferredSize(new Dimension(650, 500));
        settingsPanel.setPreferredSize(new Dimension(650, 500));
    }

    private void addComponents() {
        add(tabs);
    }

    public static void main(String[] args) {
        new GUI().setVisible(true);
    }
}

LAFs can't be mixed in the general case, they are designed such there is exactly one for all components in any application at any given time. So the outcome of mixing is simply undefined - you might or might not get away with it in a concrete context, but be prepared for unexpected visual and feel artifacts.

An example for visual artefacts (it's done in SwingX testing infrastructure, simple enough to write it out - but I'm too lazy ;-) - open the optionPane, than move it around: you'll see Nimbus striping colors appear more or less unpredictably

setLAF("Metal");
final JTable table = new JTable(new AncientSwingTeam()); 
JXFrame frame = wrapWithScrollingInFrame(table, "Metal-base");
Action action = new AbstractAction("show dialog") {

    @Override
    public void actionPerformed(ActionEvent e) {
        setLAF("Nimbus");
        JOptionPane.showMessageDialog(table, "dummy - we are Nimbus!");
        setLAF("Metal");
    }
};
addAction(frame, action);
show(frame);

The technical reason is that the ui-delegates may access properties stored in the UIManager at any time: mostly, they configure the component's properties from those stored in the UIManager at instantiation time and after that access those properties from the component. Occasionally though, they access the UIManager directly .. thus leading to unpredictable artefacts.

This solution assumes that you are going to change the Look and Feel setting for "this" particular window (a little private helper method). I used a dialog window to get input from the user (you could re engineer this yourself to hide it from the user and make the change happen when you need it). Of course this is a little long for clarity, and can be easily shortened.

private void changeLookAndFeel() {
        final LookAndFeelInfo[] list = UIManager.getInstalledLookAndFeels();
        //Look And Feels available 


            final List<String> lookAndFeelsDisplay = new ArrayList<>();
            final List<String> lookAndFeelsRealNames = new ArrayList<>();

            for (LookAndFeelInfo each : list) {
                lookAndFeelsDisplay.add(each.getName()); //simplified name of each available look and feel
                lookAndFeelsRealNames.add(each.getClassName()); //class name
            }

            if (lookAndFeelsDisplay.size() != lookAndFeelsRealNames.size()) {
                throw new InternalError(); //should never happen, redundant
            }

            String changeSpeed = (String) JOptionPane.showInputDialog(this, "Choose Look and Feel Here\n(these are all available on your system):", "Choose Look And Feel", JOptionPane.QUESTION_MESSAGE, null, lookAndFeelsDisplay.toArray(), null);

            boolean update = false;
            if (changeSpeed != null && changeSpeed.length() > 0) {
                for (int a = 0; a < lookAndFeelsDisplay.size(); a++) {
                    if (changeSpeed.equals(lookAndFeelsDisplay.get(a))) {
                        try {
                            UIManager.setLookAndFeel(lookAndFeelsRealNames.get(a)); //reads the identical class name at the corresponding index position.
                            this.whichLookAndFeel = changeSpeed;
                            update = true;
                            break;
                        }
                        catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                            err.println(ex);
                            ex.printStackTrace();
                            Logger.getLogger(MyClass.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            }

            if (update) {
                // make updates here...
            }
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top