2

(This problem occurs on Mac OS X 10.10 using Java 1.7.0_45. On Windows 7 using Java 1.7.0_55 I do not have this problem.)

I have a translucent "always on top" JFrame with some information in a JLabel. When the information changes, I inform the JFrame to pack() again, to keep the JFrame as small as possible. When the JFrame re-lays out and paints the JFrame after the resize, there are rendering glitches.

Here's a SSCCE:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ScratchSpace {


    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {

                final JLabel label = new JLabel("Test message that is long");
                label.setOpaque(false);
                label.setForeground(Color.WHITE);

                JLabel iconLabel = new JLabel("Label 2");

                JPanel contentPane = new JPanel();
                contentPane.setOpaque(false);
                contentPane.add(label);

                contentPane.add(iconLabel);

                final JFrame hudFrame = new JFrame();
                hudFrame.setAlwaysOnTop(true);
                hudFrame.setType(Window.Type.UTILITY);
                hudFrame.setUndecorated(true);
                hudFrame.setBackground(new Color(0, 0, 0, 50));

                hudFrame.setContentPane(contentPane);
                hudFrame.pack();
                hudFrame.setLocationRelativeTo(null);
                hudFrame.setVisible(true);

                Timer timer = new Timer(2000, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        label.setText("Shorter test message");
                        hudFrame.pack();
                    }
                });
                timer.setRepeats(false);
                timer.start();
            }
        });
    }
}

Here's a magnified screenshot of the output, in which you can see the JFrame has not re-rendered correctly. There is a pale impression of the JFrame's previous contents:

enter image description here

What can I do to fix this problem, while keeping per-pixel translucency?

Steve McLeod
  • 51,737
  • 47
  • 128
  • 184
  • As it works correctly on OS X 10.9 with Java 7 or 8, you may have to wait for a release version; a similar problem on 10.5 with Java 6 is examined [here](http://stackoverflow.com/a/2166500/230513). – trashgod Oct 03 '14 at 15:09
  • @trashgod, were you able to test on OS X 10.9? – Steve McLeod Oct 03 '14 at 20:04

1 Answers1

2

Info about what's going on

The "pale impression" you are seeing is the Mac OS X window shadow. The WindowServer computes the shadow as a blur of the alpha channel of the window contents.

However, WindowServer does not recompute the shadow whenever the window contents change (because this would be very expensive) but only on request. So what you need here is a way to ensure that your Swing window makes that request internally.

Possible workaround

Unfortunately, I don't have 10.10 around to test on, so I don't know if this works, but there is one case where shadow recalculation is obviously necessary: when the window is resized (because even non-transparent windows get shadows then). In this case, you are resizing the window with pack(), so there must be some kind of timing/ordering-of-events issue here.

My suggestion, then, is to resize the window slightly after the content has been updated. Pseudocode:

  1. Do your content update and pack() as you currently do.
  2. Change the size of the JFrame to be one pixel larger than the packed size in one dimension.
  3. Start a timer to execute 0 ms later which does:

    1. pack() again, or just change the size to be one pixel smaller.

Straightforward ugly revision of your code:

            Timer timer = new Timer(2000, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    label.setText("Shorter test message");
                    hudFrame.pack();
                    Dimension d = hudFrame.getSize();
                    d.width += 1;
                    hudFrame.setSize(d);
                    Timer timer = new Timer(0, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            hudFrame.pack();
                        }
                    });
                    timer.setRepeats(false);
                    timer.start();
                }
            });
            timer.setRepeats(false);
            timer.start();
Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
  • Kevin, I tried this approach, and it didn't work properly, but in a different way than before - now I get something that looks the like the rendered object on top of itself... Your response reminded me that I once heard of the Apple-specific apple.awt.windowShadow.revalidateNow property, so I'm trying that. – Steve McLeod Oct 03 '14 at 20:03