views:

711

answers:

2

I'm trying to create a translucent window with Java on OSX and add a JLabel to it.

This JLabel changes its text every second....

alt text

However the component is not repainting well.

How can I solve this problem?

I've found the these articles, but I can't figure out how to solve it.

If possible, please paste the fixing source code, here's mine:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import java.awt.Color;
import java.awt.Font;
import java.util.Timer;
import java.util.TimerTask;

public class Translucent {
    public static void main( String [] args ) {

        JFrame frame = new JFrame();

        frame.setBackground( new Color( 0.0f,0.0f,0.0f,0.3f));

        final JLabel label =  new JLabel("Hola");
        label.setFont( new Font( label.getFont().getFamily(), Font.PLAIN, 46 ) );
        label.setForeground( Color.white );

        frame.add( label );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );

        Timer timer = new Timer();
        timer.schedule( new TimerTask(){
            int i = 0;
            public void run() {
                label.setText("Hola "+ i++ );
            }
        }, 0, 1000 );


    }   
}
+2  A: 

The problem may also have to do with the fact that you're setting the JLabel's text from a thread that is not the Event Dispatch Thread.

There are two ways to solve this. Without testing your problem, I'd solve it by using the javax.swing.Timer class, instead of the java.util.Timer class. javax.swing.Timer will ensure that events are fired on the dispatch thread.

So (untested code):

final ActionListener labelUpdater = new ActionListener() {
  private int i;
  @Override
  public final void actionPerformed(final ActionEvent event) {
    label.setText("Hola " + this.i++);
  }
};
final javax.swing.Timer timer = new javax.swing.Timer(1000L, labelUpdater);

The other way to solve it is to continue to use java.util.Timer but to make sure that you use EventQueue.invokeLater(Runnable) to ensure that updates to the label take place on the EDT.

Laird Nelson
In case it's not clear, if you do Swing-related stuff OFF the EDT, then you get artifacts like you're experiencing--paint events, for example, might arrive out of order.jjnguy: a revalidation should be automatically triggered already by the JLabel setText() method; the reason a repaint() event might work here is because it will be scheduled on the EDT for later execution. It's better to make sure the initial setText() call was done on the EDT in the first place.
Laird Nelson
I tried switching the timertask to this: timer.schedule( new TimerTask(){ int i = 0; public void run() { SwingUtilities.invokeLater(new Runnable(){ public void run(){ label.setText("Hola "+ i++ ); } }); } }, 0, 1000 );And it didn't work. Sun Bug: http://bugs.sun.com/view_bug.do?bug_id=4297006states that you may have to override paintComponent() because opaqueness and translucent don't interact well.
Kylar
@Laird. Yeap, I tried actually with `SwingUtilities.invokeLater` which has the same effect ( send the message in the EDT ) with exactly the same results. I remove it before posting to make the code brief.
OscarRyz
@Liard: I've tried your code ( just to be sure ) but it has exactly the same effect.
OscarRyz
+2  A: 

I've had some luck extending JLabel and implementing Icon to get a translucent component working the way I want. You can see the result of various rule combinations in this AlphaCompositeDemo. The example below is 100% white atop 50% black.

Addendum: Note how this example composites opaque text on a clear offscreen background over the translucent frame background.

Addendum: Here's a way to make the whole frame translucent. Unfortunately, it dims the content, too.

alt text

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Translucent extends JPanel implements ActionListener {

    private static final int W = 300;
    private static final int H = 100;
    private static final Font font =
        new Font("Serif", Font.PLAIN, 48);
    private static final SimpleDateFormat df =
        new SimpleDateFormat("HH:mm:ss");
    private final Date now = new Date();
    private final Timer timer = new Timer(1000, this);
    private BufferedImage time;
    private Graphics2D timeG;

    public Translucent() {
        super(true);
        this.setPreferredSize(new Dimension(W, H));
        timer.start();
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        int w = this.getWidth();
        int h = this.getHeight();
        g2d.setComposite(AlphaComposite.Clear);
        g2d.fillRect(0, 0, w, h);
        g2d.setComposite(AlphaComposite.Src);
        g2d.setPaint(g2d.getBackground());
        g2d.fillRect(0, 0, w, h);
        renderTime(g2d);
        int w2 = time.getWidth() / 2;
        int h2 = time.getHeight() / 2;
        g2d.setComposite(AlphaComposite.SrcOver);
        g2d.drawImage(time, w / 2 - w2, h / 2 - h2, null);
    }

    private void renderTime(Graphics2D g2d) {
        g2d.setFont(font);
        String s = df.format(now);
        FontMetrics fm = g2d.getFontMetrics();
        int w = fm.stringWidth(s);
        int h = fm.getHeight();
        if (time == null && timeG == null) {
            time = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
            timeG = time.createGraphics();
            timeG.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
            timeG.setFont(font);
        }
        timeG.setComposite(AlphaComposite.Clear);
        timeG.fillRect(0, 0, w, h);
        timeG.setComposite(AlphaComposite.Src);
        timeG.setPaint(Color.green);
        timeG.drawString(s, 0, fm.getAscent());
    }

    private static void create() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setBackground(new Color(0f, 0f, 0f, 0.3f));
        f.setUndecorated(true);
        f.add(new Translucent());
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        now.setTime(System.currentTimeMillis());
        this.repaint();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                create();
            }
        });
    }
}
trashgod
+1 for adding the screenshot. Looks promising. The only difference is, the text should not be translucent, but opaque ( like this screenshot in windows ) but given the current state of my code, I guess this should the be workaround for OSX. I'll give it a try tomorrow when I have my Mac at hand again. Thank you.
OscarRyz
Oh, you're not using a JLabel but painting directly on the panel... mmhhhh I have to try that.
OscarRyz
Yes, at some point you have to do a composite with the frame's background. I'll update to show how to do opaque text.
trashgod
This is great. Thank you. I'm still having a bit of a problem displaying the text, there is a "shadow" of the last number printed after a resize, but that's something I can live with. So, If I understand this right, what you do is to paint over the panel the previous number but is the AlphaComposite as "Clear" which I assume clears the panel and then you paint on top of that. Is this the principle? I'll look further to the code. Here's that little "ghost" : http://img21.imageshack.us/img21/9195/capturadepantalla201002.png
OscarRyz
Now this is odd. If I call `setUndecorated( true )` the ghost is gone. I don't have an Idea of why?... http://img36.imageshack.us/img36/9195/capturadepantalla201002.png
OscarRyz
Drat! I see it, too. I thought a `ComponentListener` might coerce it, but it appears to be a Quartz "feature"; it disappears after inconify/de-inconify. The `apple.awt.CWindow` trick didn't seem to work. Yes, setting `Clear` mode causes all subsequent drawing to clear the desitiantion pixel(s).
trashgod
I _think_ the shadow is an artifact of Apple's `Quartz` trying to optimize repaints; `setUndecorated()` may work by preventing resizing.
trashgod