Ioannis K.
Ioannis K.

Reputation: 552

Change font dynamically causes problems on some components

first, I know how to update the font of the whole UI. I use the following code:

private static void setUIFont(final FontUIResource f) {
    for (Map.Entry<Object, Object> entry : UIManager.getLookAndFeelDefaults().entrySet()) {
        Object key = entry.getKey();
        Object value = UIManager.getLookAndFeelDefaults().get(key);
        if (value != null && value instanceof FontUIResource) {
            UIManager.getLookAndFeelDefaults().put(key, f);
        }
    }

    dynamicallyUpdateRootPane(f);
}


private static void dynamicallyUpdateRootPane(FontUIResource f) {
        updateComponent(rootPanel, f);
}


private static void updateComponent(Component c, FontUIResource resource) {
    if (c == null) {
        return;
    }
    if (c instanceof JComponent) {
        JComponent jc = (JComponent) c;
        jc.updateUI();
        JPopupMenu jpm = jc.getComponentPopupMenu();
        if (jpm != null) {
            updateComponent(jpm, resource);
        }
    }
    Component[] children = null;
    if (c instanceof JMenu) {
        children = ((JMenu) c).getMenuComponents();
    }
    else if (c instanceof Container) {
        children = ((Container) c).getComponents();
    }
    if (children != null) {
        for (Component child : children) {
            if (child instanceof Component) {
                updateComponent(child, resource);
            }
        }
    }
    int style = Font.PLAIN;
    Font f = c.getFont();
    if (f == null) {
        f = getFontUIResource(16); // default
    }
    if (f.isBold()) {
        style = Font.BOLD;
    }
    else if (f.isItalic()) {
        style = Font.ITALIC;
    }

    if (c instanceof JEditorPane) {
        ((JEditorPane) c).putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
    }
    c.setFont(resource.deriveFont(style));
}

After I set the keys I need to update my root panel recursively because not all components change its appearance. This code works almost 95%. I have some problems with JButton's and JMenuItem's.

Lets say I launch my program with a large font size and change it dynamically to a smaller one. The font of my Buttons changes (thats good...) but when I hover them the font changes from small to big. When I put my mouse away it changes again from big to small and I have no clue why or how to handle it, tested A LOT but nothing seems to work. There's this roll over effect and it seems that it uses a different font when I hover the element.

The other weird thing is my menu items. The menubar changes its font (the menus) but the menu items do not change. I tried to update them manually e.g. to set the font manually, but it does not work at all.

Hope you guys can help me because I spent too many hours (even days) on this. Btw. I am using Nimbus.

Best regards

** UPDATE **

Fixed my code and my working example. Now all components on my GUI will be displayed correctly after I change the font size dynamically. Have atm. no time to clean my code but for those who are interested in a solution the code should be clear.

    import java.awt.Component;
    import java.awt.Container;
    import java.awt.FlowLayout;
    import java.awt.Font;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.Map;

    import javax.swing.JButton;
    import javax.swing.JComboBox;
    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.UIManager.LookAndFeelInfo;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.plaf.FontUIResource;



    public class Test extends JFrame {

        /**
         * 
         */
        private static final long serialVersionUID = 1865556053669711743L;


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


        public Test() {
            setLaf();
            prepareFrame();
            setJMenuBar(new MyMenuBar());
            pack();
            setVisible(true);
        }


        private void prepareFrame() {
            setLayout(new FlowLayout(FlowLayout.RIGHT));
            final JComboBox<String> combo = new JComboBox<>(new String[] {
                            "Small", "Large", "Larger"
            });
            JButton button = new JButton("Change");

            button.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    int index = combo.getSelectedIndex();
                    switch (index) {
                        case 0:
                            setUIFont(getFontUIResource(14));
                            break;
                        case 1:
                            setUIFont(getFontUIResource(16));
                            break;
                        case 2:
                            setUIFont(getFontUIResource(17));
                            break;
                    }
                    pack();
                    //SwingUtilities.updateComponentTreeUI(Test.this);
                }
            });
            getContentPane().add(combo);
            getContentPane().add(button);
        }


        private void setLaf() {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    try {
                        UIManager.setLookAndFeel(info.getClassName());
                    }
                    catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                                    | UnsupportedLookAndFeelException e) {
                        e.printStackTrace();
                    }
                }
            }
        }


        private class MyMenuBar extends JMenuBar {

            /**
             * 
             */
            private static final long serialVersionUID = 1434003372646915700L;


            public MyMenuBar() {
                JMenu menu1 = new JMenu("File");
                JMenu menu2 = new JMenu("Help");

                menu1.add(new JMenuItem("This is a test"));
                menu1.add(new JMenuItem("This is a test"));
                menu1.add(new JMenuItem("This is a test"));
                menu1.add(new JMenuItem("This is a test"));
                menu2.add(new JMenuItem("This is a test"));
                menu2.add(new JMenuItem("This is a test"));

                add(menu1);
                add(menu2);
            }
        }


        private FontUIResource getFontUIResource(int size) {
            return new FontUIResource(new Font("Arial", Font.PLAIN, size));
        }


        private void setUIFont(final FontUIResource f) {
            for (Map.Entry<Object, Object> entry : UIManager.getLookAndFeelDefaults().entrySet()) {
                Object key = entry.getKey();
                Object value = UIManager.getLookAndFeelDefaults().get(key);
                if (value != null && value instanceof FontUIResource) {
                    UIManager.getLookAndFeelDefaults().put(key, f);
                }
            }

            dynamicallyUpdateRootPane(f);
        }


        private void dynamicallyUpdateRootPane(FontUIResource f) {
            updateComponent(this, f);
        }


private void updateComponent(Component c, FontUIResource resource) {
    if (c == null) {
        return;
    }
    if (c instanceof JComponent) {
        JComponent jc = (JComponent) c;
        jc.updateUI();
        JPopupMenu jpm = jc.getComponentPopupMenu();
        if (jpm != null) {
            updateComponent(jpm, resource);
        }
    }
    Component[] children = null;
    if (c instanceof JMenu) {
        children = ((JMenu) c).getMenuComponents();
    }
    else if (c instanceof Container) {
        children = ((Container) c).getComponents();
    }
    if (children != null) {
        for (Component child : children) {
            if (child instanceof Component) {
                updateComponent(child, resource);
            }
        }
    }
    int style = Font.PLAIN;
    Font f = c.getFont();
    if (f == null) {
        f = getFontUIResource(16); // default
    }
    if (f.isBold()) {
        style = Font.BOLD;
    }
    else if (f.isItalic()) {
        style = Font.ITALIC;
    }

    if (c instanceof JEditorPane) {
        ((JEditorPane) c).putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
    }
    c.setFont(resource.deriveFont(style));
    }

Upvotes: 3

Views: 1220

Answers (2)

Ioannis K.
Ioannis K.

Reputation: 552

Finally I solved my problem. @camickr gave me a good hint. Had to modify the API code to prevent some NPEs. I do not have time to make my code "cleaner" but for those who are interested I will share my code: All items change their size correctly now.

private void updateComponent(Component c, FontUIResource resource) {
    if (c == null) {
        return;
    }
    if (c instanceof JComponent) {
        JComponent jc = (JComponent) c;
        jc.updateUI();
        JPopupMenu jpm = jc.getComponentPopupMenu();
        if (jpm != null) {
            updateComponent(jpm, resource);
        }
    }
    Component[] children = null;
    if (c instanceof JMenu) {
        children = ((JMenu) c).getMenuComponents();
    }
    else if (c instanceof Container) {
        children = ((Container) c).getComponents();
    }
    if (children != null) {
        for (Component child : children) {
            if (child instanceof Component) {
                updateComponent(child, resource);
            }
        }
    }
    int style = Font.PLAIN;
    Font f = c.getFont();
    if (f == null) {
        f = getFontUIResource(16); // default
    }
    if (f.isBold()) {
        style = Font.BOLD;
    }
    else if (f.isItalic()) {
        style = Font.ITALIC;
    }

    if (c instanceof JEditorPane) {
        ((JEditorPane) c).putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
    }
    c.setFont(resource.deriveFont(style));
}

** EDIT ** Added support for JEditorPanes.

Upvotes: 0

camickr
camickr

Reputation: 324118

Instead of writing your own recursive code, maybe you can just treat this like a LAF change and just invoke:

SwingUtilities.updateComponentTreeUI(frame);

See the section from the Swing tutorial on Changing the LAF.

Also, there is no need to invoke revalidate() and repaint() in your code. The setFont(...) method will invoke those methods automatically.

Upvotes: 2

Related Questions