Search code examples
javaswingfontsjmenu

Update JMenu and JMenu font after JMenuBar is visible


The following code below creates a simple GUI with a button in the centre which, when clicked, should update the font of the JMenuBar components. To do this a method setMyFont fires in an ActionListener on the JButton. However after several listed attempts I have failed to accomplish this but I am unaware of why. The code used in setMyFont is below

public void setMyFont(Font f, Font f2) {
    //Attempt 1 in the hope it would autodetect that font
    //had changed and just update
    menuFont = f;
    menuItemFont = f2;

    //Attempt 2 in the hope on the repaint it would update 
    //the font with the new UIManager properties
    UIManager.put("Menu.font", menuFont);
    UIManager.put("MenuItem.font", menuItemFont);

    //Attempt 3 in the hope that going over each component 
    //individually would update the font
    for(Component comp: getComponents()) {
        if(comp instanceof JMenu) {
            comp.setFont(menuFont);
        } else {
            comp.setFont(menuItemFont);
        }
    }

    validate();
    repaint();
}

Is there a reason that the font doesn't update on the components with the current code? Also how could I change my code to allow for the font to update on the components even though they are already created?


Full Code for SSCCE

import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.UIManager;

public class Main extends JFrame {
    private static final long serialVersionUID = 3206847208968227199L;
    JButton but;
    MenuBar mB;

    private Main() {
        setSize(600, 600);

        mB = new MenuBar();
        setJMenuBar(new MenuBar());

        but = new JButton("Change Font");
        but.addActionListener(new CustomActionListener());
        add(but);

        setVisible(true);
        setLocationRelativeTo(null);
    }

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

    private class MenuBar extends JMenuBar {
        private static final long serialVersionUID = -2055260049565317972L;
        Font menuFont = new Font("Courier", Font.ITALIC + Font.BOLD, 12);
        Font menuItemFont = new Font("sans-serif", 0, 12);
        JMenu menu, subMenu;

        MenuBar() {
            UIManager.put("Menu.font", menuFont);
            UIManager.put("MenuItem.font", menuItemFont);

            menu = new JMenu("Menu");

            subMenu = new JMenu("Sub Menu");
            subMenu.add(new JMenuItem("Sub Item"));
            subMenu.add(new JMenu("Sub Menu"));
            menu.add(subMenu);

            menu.add(new JMenuItem("Sub Item"));
            menu.add(new JMenu("Sub Menu"));

            add(menu);

            menu = new JMenu("Another Menu");
            menu.add(new JMenu("Sub Menu"));
            menu.add(new JMenuItem("Sub Item"));
            menu.add(new JMenu("Sub Menu"));
            add(menu);
        }

        public void setMyFont(Font f, Font f2) {
            //Attempt 1 in the hope it would autodetect that font
            //had changed and just update
            menuFont = f;
            menuItemFont = f2;

            //Attempt 2 in the hope on the repaint it would update 
            //the font with the new UIManager properties
            UIManager.put("Menu.font", menuFont);
            UIManager.put("MenuItem.font", menuItemFont);

            //Attempt 3 in the hope that going over each component 
            //individually would update the font
            for(Component comp: getComponents()) {
                if(comp instanceof JMenu) {
                    comp.setFont(menuFont);
                } else {
                    comp.setFont(menuItemFont);
                }
            }

            validate();
            repaint();
        }
    }

    private class CustomActionListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            mB.setMyFont(new Font("sans-serif", 0, 12), new Font("Courier", Font.ITALIC + Font.BOLD, 12));
        }
    }
}

Solution

    1. After setting the font, you need to have each component in the hierarchy call update it's UI - SwingUtilities has a convenience method for this

      UIManager.put("Menu.font",  menuFont);
      SwingUtilities.updateComponentTreeUI( Main.this );
      
    2. Use the FontUIResource class eg

      FontUIResource menuFont = new FontUIResource("Courier", Font.ITALIC + Font.BOLD, 12);
      

    The following code adapted from the posted SSCCE works for me:

    import java.awt.Font;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JMenu;
    import javax.swing.JMenuBar;
    import javax.swing.JMenuItem;
    import javax.swing.SwingUtilities;
    import javax.swing.UIManager;
    import javax.swing.plaf.FontUIResource;
    
    public class Main extends JFrame {
        private static final long serialVersionUID = 3206847208968227199L;
        JButton but;
        MenuBar mB;
    
        private Main() {
            setSize(600, 600);
    
            mB = new MenuBar();
            setJMenuBar(mB);
    
            but = new JButton("Change Font");
            but.addActionListener(new CustomActionListener());
            add(but);
    
            setVisible(true);
            setLocationRelativeTo(null);
        }
    
        public static void main(String[] args) {
            new Main();
        }
    
        private class MenuBar extends JMenuBar {
            private static final long serialVersionUID = -2055260049565317972L;
            Font menuFont = new FontUIResource("Courier", Font.ITALIC + Font.BOLD, 12);
            Font menuItemFont = new FontUIResource("sans-serif", 0, 12);
            JMenu menu, subMenu;
    
            MenuBar() {
                UIManager.put("Menu.font", menuFont);
                UIManager.put("MenuItem.font", menuItemFont);
    
                menu = new JMenu("Menu");
    
                subMenu = new JMenu("Sub Menu");
                subMenu.add(new JMenuItem("Sub Item"));
                subMenu.add(new JMenu("Sub Menu"));
                menu.add(subMenu);
    
                menu.add(new JMenuItem("Sub Item"));
                menu.add(new JMenu("Sub Menu"));
    
                add(menu);
    
                menu = new JMenu("Another Menu");
                menu.add(new JMenu("Sub Menu"));
                menu.add(new JMenuItem("Sub Item"));
                menu.add(new JMenu("Sub Menu"));
                add(menu);
            }
    
            public void setMyFont(Font f, Font f2) {
    
                menuFont = f;
                menuItemFont = f2;
                UIManager.put("Menu.font", menuFont);
                UIManager.put("MenuItem.font", menuItemFont);
                SwingUtilities.updateComponentTreeUI(Main.this);
            }
        }
    
        private class CustomActionListener implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                mB.setMyFont(new FontUIResource("sans-serif", 0, 12), new FontUIResource("Courier", Font.ITALIC + Font.BOLD, 12));
            }
        }
    }