Search code examples
javaswing

Zooming feature does not work if I use setFont with on a label/text area


I've implemented a zooming feature for all of my applications. But it does not work if I use setFont on a component. There are labels and text areas where I want a specific type (italic and/or bold) or a specific font family (i.e. san serif). If I use setFont() when creating these components, the zoom feature does not work. I've tried catching setFont for the components but they are never called. Any ideas why? My zoom class/component is attached (thank you Stack Overflow for the base code!)

public final class ZoomControl extends JPanel implements ActionListener {
// --------------------------------------------------------------------------
// variables
// --------------------------------------------------------------------------
    private final Frame mFrame;
    private final JLabel mText = new JLabel(" Zoom ");
    private final JMenuItem mIncrease = new JMenuItem(" + ");
    private final JMenuItem mDecrease = new JMenuItem(" - ");
// --------------------------------------------------------------------------
// constructor
// --------------------------------------------------------------------------
    /**
     * @param frame Specifies the frame upon which to update after changing
     *              the fonts.
     */
    public ZoomControl(final Frame frame) {
        super(new FlowLayout());
        CommonUtils.checkParameterNull("frame", frame);
        mFrame = frame;
        add(mIncrease);
        add(mText);
        add(mDecrease);
        mIncrease.addActionListener(this);
        mIncrease.setBorder(BorderFactory.createEmptyBorder());
        mDecrease.addActionListener(this);
        mDecrease.setBorder(BorderFactory.createEmptyBorder());
    }
// --------------------------------------------------------------------------
// methods
// --------------------------------------------------------------------------
    /**
     * @see ActionListener#actionPerformed(ActionEvent)
     */
    @Override
    public void actionPerformed(final ActionEvent evt) {
        Object obj = evt.getSource();
        if (obj ==  mIncrease) {
            changeUIFonts(1);
        } else if (obj == mDecrease) {
            changeUIFonts(-1);
        }
    }
    /** Change all the fonts for the application as specified by the size.
     * @param size
     */
    private void changeUIFonts(final int size) {
        UIDefaults myDefaults = UIManager.getDefaults();
        for (Enumeration<?> e = myDefaults.keys(); e.hasMoreElements(); ) {
            final Object key = e.nextElement();
            final Object value = myDefaults.get(key);
            if (value instanceof Font) {
                final Font font = (Font) value;
                final int newSize = Math.round(font.getSize() + size);
                if (value instanceof FontUIResource) {
                    myDefaults.put(key, new FontUIResource(font.getName(),
                                    font.getStyle(), newSize));
                } else {
                    myDefaults.put(key, new Font(font.getName(),
                                    font.getStyle(), newSize));
                }
            }
        }
        SwingUtilities.updateComponentTreeUI(mFrame);
        mFrame.pack();
    }
} ```

Solution

  • Solution

    You're iterating over UIManager.getDefaults(). This is important if you ever plan to adding new components later in your session. (If your UI never changes: then you can avoid iterating over UIManager at all.)

    But you'll need to also iterate over all existing the JComponents.

    I recommend this approach:

        private void changeUIFonts(final int size) {
            UIDefaults myDefaults = UIManager.getDefaults();
            for (Enumeration<?> e = myDefaults.keys(); e.hasMoreElements(); ) {
                final Object key = e.nextElement();
                final Object value = myDefaults.get(key);
                if (value instanceof Font) {
                    final Font font = (Font) value;
                    final int newSize = Math.round(font.getSize() + size);
                    if (value instanceof FontUIResource) {
                        myDefaults.put(key, new FontUIResource(font.getName(),
                                font.getStyle(), newSize));
                    } else {
                        myDefaults.put(key, new Font(font.getName(),
                                font.getStyle(), newSize));
                    }
                }
            }
    
            changeUIFontsForComponent(mFrame, size);
    
            SwingUtilities.updateComponentTreeUI(mFrame);
            mFrame.pack();
        }
    
        private void changeUIFontsForComponent(Component c, int fontSizeDelta) {
            Font f = c.getFont();
            if (!(f instanceof FontUIResource)) {
                f = new Font(f.getName(), f.getStyle(), Math.round(f.getSize() + fontSizeDelta));
                c.setFont(f);
            }
            if (c instanceof Container container) {
                for (Component child : container.getComponents()) {
                    changeUIFontsForComponent(child, fontSizeDelta);
                }
            }
        }
    

    Testing

    I tested this by adding this main method to your code sample:

    public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame f = new JFrame();
                    JPanel p = new JPanel(new FlowLayout());
                    ZoomControl c = new ZoomControl(f);
                    p.add(c);
                    JLabel label = new JLabel("Extra label");
                    label.setFont(new Font("dialog", Font.BOLD, 18));
                    p.add(label);
                    f.getContentPane().add(p);
                    f.pack();
                    f.setVisible(true);
                }
            });
        }