Search code examples
javawindowsswinglook-and-feeljspinner

Java Swing right border not working on Microsoft Windows


I can't get the standard BorderFactory.createEmptyBorder working properly in Java Swing.

I tried it with a simple Java app that creates a Spinner View and declares an empty border around it. The border is shown everywhere except for the right side of the spinner view. but that only happened in Windows, on OSX it works as intended.

To clarify the use of a border: In my real application I have a visible border at the outside of the spinner and then I want to center the text inside. I found the createCompoundBorder property very useful for that.

import javax.swing.*;
import java.awt.*;
import javax.swing.border.Border;

class HelloWorldSwing {
    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(
                UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {}
        
        Runnable guiCreator = new Runnable() {
            public void run() {
                JFrame fenster = new JFrame("Hallo Welt mit Swing");
                fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                SpinnerNumberModel model = new SpinnerNumberModel(1,1,9,1);
                JSpinner spinner = new JSpinner(model);
                
                JFormattedTextField textField = ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField();
                textField.setOpaque(true);
                // textField.setBackground(Color.);
                
                spinner.setBorder(BorderFactory.createEmptyBorder(50, 100, 50, 100));
                
                spinner.setUI(new javax.swing.plaf.basic.BasicSpinnerUI() {
                    protected Component createNextButton() {
                        Component c = new JButton();
                        c.setPreferredSize(new Dimension(0, 0));
                        c.setFocusable(false);
                        return c;
                    }

                    protected Component createPreviousButton() {
                        Component c = new JButton();
                        c.setPreferredSize(new Dimension(0, 0));
                        c.setFocusable(false);
                        return c;
                    }
                });
                spinner.setBackground(Color.LIGHT_GRAY);
                fenster.add(spinner);

                fenster.setSize(300, 200);
                fenster.setVisible(true);
            }
        };

        SwingUtilities.invokeLater(guiCreator);
    }
}

= I don't want to find a solution that implements one more UI element. This would be an easy and simple solution (Thanks to Andrew Thompson). The reason is that it is just a small project where I got into this error. In small projects I mostly want a clean and good looking code, which means that such bugs are fixed by trying to fix the broken code and not by doing a workaround.


Solution

  • It looks like a JSpinner is using a custom layout manager and is not handling the Border correctly.

    I modified your code to give the buttons the width of the right edge of the border minus 1. I guess the layout manager leaves a 1 pixel gap between the edge of the text field and the button.

    Seems to work on Windows, but it might mess up OSX?

    import javax.swing.*;
    import java.awt.*;
    import javax.swing.border.Border;
    
    class HelloWorldSwing {
        public static void main(String[] args) {
            try {
                UIManager.setLookAndFeel(
                    UIManager.getSystemLookAndFeelClassName());
            } catch (Exception e) {}
    
            Runnable guiCreator = new Runnable() {
                public void run() {
                    JFrame fenster = new JFrame("Hallo Welt mit Swing");
                    fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
                    SpinnerNumberModel model = new SpinnerNumberModel(1,1,9,1);
                    JSpinner spinner = new JSpinner(model);
    
                    JFormattedTextField textField = ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField();
                    textField.setOpaque(true);
                    // textField.setBackground(Color.);
    
                    spinner.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 0));
    
                    spinner.setUI(new javax.swing.plaf.basic.BasicSpinnerUI() {
                        protected Component createNextButton() {
                            Component c = new JButton();
                            c.setPreferredSize(new Dimension(9, 0));
                            c.setVisible(false);
                            return c;
                        }
    
                        protected Component createPreviousButton() {
                            Component c = new JButton();
                            c.setPreferredSize(new Dimension(9, 0));
                             c.setVisible(false);
                            return c;
                        }
                    });
                    spinner.setBackground(Color.LIGHT_GRAY);
                    fenster.setLayout( new FlowLayout() );
                    fenster.add(spinner);
    
                    fenster.setSize(300, 200);
                    fenster.setVisible(true);
                }
            };
    
            SwingUtilities.invokeLater(guiCreator);
        }
    }
    

    The other solution is to not use a JSpinner. You could easily use a JTextField and write a custom Action for the Up/Down arrows. A little more work but it will work on all platforms.

    Also since you seems to be worried about creating extra components this will be far more efficient in that regard. The JSpinner is a complex component itself and the JFormattedTextField is far more complex than a simple JTextField.