Search code examples
javaswingtransparencyrepaint

Swing transparent background not being repainted


I have a problem using transparent backgrounds in Swing. There are a lot of artefacts produced as swing is not repainting changed regions.

buggy

As far as I can tell there are 2 out-of-the-box ways to use transparent backgrounds:

  1. an opaque component with transparent color set as background (left txt field)

Problem: the transparent part of the background is never refreshed -> Artefacts.

  1. an non-opaque component with transparent color set as background (right txt field)

Problem: background is not being drawn at all.

What I do not want to do:

  • to use timers to auto repaint the frame (super awful)
  • to override paintComponent method (which actually works, but is really really awful)

I am running on Win7 x64

Aaaand here is my SSCCEEE:

Update 1: init with invokeLater (still won't work)

public class OpacityBug {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new OpacityBug();
            }
        });
    }

    static final Color transparentBlue = new Color(0f, 1f, 0f, 0.5f); 

    JFrame frame;
    JPanel content;

    JTextField txt1;
    JTextField txt2;

    public OpacityBug() {
        initFrame();
        initContent();
    }

    void initFrame() {
        frame = new JFrame();
        frame.setSize(300, 80);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    void initContent() {
        content = new JPanel();
        content.setDoubleBuffered(true);
        content.setBackground(Color.red);
        frame.getContentPane().add(content);

        txt1 = new JTextField() {
            @Override
            public void setBorder(Border border) {
                super.setBorder(null); //nope border
            }
        };
        txt1.setText("Hi! I am Buggy!");
        txt1.setOpaque(true);
        txt1.setBackground(transparentBlue);
        content.add(txt1);

        txt2 = new JTextField() {
            @Override
            public void setBorder(Border border) {
                super.setBorder(null); //nope border
            }
        };
        txt2.setText("And I have no BG!");
        txt2.setOpaque(false);
        txt2.setBackground(transparentBlue);
        content.add(txt2);

        content.revalidate();
        content.repaint();
    }
}

Update 2

As some of you noticed, it seems Swing that swing is unable to paint transparent backgrounds. But it is not (yet) clear to me why, I searched for the piece of code responsible for drawing the background of the component and have found the following code in ComponentUI.java :

public void update(Graphics g, JComponent c) {
if (c.isOpaque()) {
    g.setColor(c.getBackground());
    g.fillRect(0, 0, c.getWidth(),c.getHeight());
}
paint(g, c);
}

As you can see, it assumes that if a component is not opaque the background doesn't need to be repainted. I say that this is a very vague assumption.

I would propose following implementation:

public void update(Graphics g, JComponent c) {
if(c.isOpaque() || (!c.isOpaque() && c.isBackgroundSet())) {
    g.setColor(c.getBackground());
    g.fillRect(0, 0, c.getWidth(), c.getHeight());
}
paint(g, c);
}

I simply check if the background was set at all when the component is not opaque. This simple addition would allow us to use transparent backgrounds in swing. At least I do not know any reasons why it should not be done that way.


Solution

  • By using a semi-transparent background you are breaking Swings painting rules and Swing doesn't handle this automatically.

    Basically, when the component is opaque, you promise to paint the background of the component. However, when the background is semi-transparent it is not cleared completely so you get painting artifacts.

    The solution is to make the component non-opaque, which means the parent component will be painted first to completely clear the background, and then the component will paint the semi-transparent background. Something like:

    JPanel panel = new JPanel()
    {
        protected void paintComponent(Graphics g)
        {
            g.setColor( getBackground() );
            g.fillRect(0, 0, getWidth(), getHeight());
            super.paintComponent(g);
        }
    };
    panel.setOpaque(false); // background of parent will be painted first
    panel.setBackground( new Color(255, 0, 0, 20) );
    frame.add(panel);
    

    Check out Backgrounds With Transparency for more information. The link also contains a class called the AlphaContainer which allows you to wrap any component with a transparent background.

    Also, note this only applies when using partial transparency. If you want full transparency then simply make the component non-opaque:

    component.setOpaque( false );