简体   繁体   中英

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.

越野车

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.

By using a transparent background you are breaking Swings painting rules. Basically when the component is opaque you promise to paint the background of the component. But because the background is transparent there is nothing to paint.

Check out Backgrounds With Transparency for more information and a couple of simple solutions.

You should respect Swing's threading policy and have the GUI initialized on the GUI thread:

SwingUtilities.invokeLater(() -> new OpacityBug());

I can't tell whether this is all it will take to correct the behavior on your side, but it has done just that on mine (OS X).

I cannot stress this enough, I see a ton of people not doing this while it is vital to ensure correct behavior from Swing; ALL GUI instances must run on the EDT(Event Dispatch Thread)

Please read the below article and adjust your code and report back with the effects.

https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html

You should use setOpaque(false) on the component that has a problem, and do the same with all the parents !

For example, if you have a JList 'jList' inside a JScrollPane 'scrollPane', the whole thing being inside a JPanel 'jPanel', you should use :

jList.setOpaque(false);
scrollPane.setOpaque(false);
scrollPane.getViewport().setOpaque(false);
jPanel.setOpaque(false);

And yes, if you have a JScrollPane , you should set its viewport's opacity to false as well.

This will prevent painting problems on your components with transparent backgrounds.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM