Re-Paint on Translucent Frame/Panel/Component

Re-paint on translucent frame/panel/component.

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.

image

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();
}
});
}
}

How to repaint a Panel from inside

Your paintComponent method is doing many things that it should never do, such as create and place components. It should paint and paint only.

I suggest that instead you place your components into your GUI inside of your class's constructor, and again use paintComponent just for painting and nothing else. If you're wanting to display a calendar, consider displaying each day as a JPanel held in a GridLayout-using JPanel. The DayPanel could be in its own class in order to refactor out the information.

Also, you should rename your class, since by convention class names should begin with an upper case letter, and so something like CalendarPanel would be better.

Parent Panel not repainting correctly

The parent panel does not actually get automatically repainted completely. paintComponent() is called, but if you check the clipping, for example by:

System.out.println(g.getClipBounds());

you'll see that only the area below the smaller component is painted. (The component is not opaque, so the parent component needs to paint the area below it). You need to call repaint() explicitly for the parent panel:

getParent().repaint();

(Or using the repaint() variants that specify the region, if the parent component is expensive to draw and can optimize partial draws).

Repaint without painting every component again

To me, it seems a bit silly to put the functionality in the JFrame when what you seem to want is a container which can fade it's content in and out. This way you can isolate the responsibility to a single container/class which can be placed or used in what ever way you want in isolation to the rest of the UI.

Basically, this example uses a FadingPane (based on a JPanel) to control the fading process, but onto which I place JLabel which holds the actual images.

Fading is controlled through the use of a AlphaComposite, meaning that this panel will actually physically fade in and out, not just change fill color ;)

There is also a FadingListener which provides additional notifications about the fading process, really only interested in fadeOutDidComplete, so you can switch the images and fade the panel back in, but you never know...

Fading

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

public static void main(String[] args) {
new Test();
}

public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}

JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

public class TestPane extends JPanel {

private JLabel label;
private FadingPane fadingPane;

private File[] pictures;
private int index;

public TestPane() {
// Just for show
setBackground(Color.RED);
fadingPane = new FadingPane(new FadeListener() {
@Override
public void fadeDidStart(FadingPane panel) {
}

@Override
public void fadeDidStop(FadingPane panel) {
}

@Override
public void fadeOutDidComplete(FadingPane panel) {
nextPicture();
fadingPane.fadeIn();
}

@Override
public void fadeInDidComplete(FadingPane panel) {
}
});
setLayout(new BorderLayout());
fadingPane.setLayout(new BorderLayout());
label = new JLabel();
fadingPane.add(label);

add(fadingPane);

JButton next = new JButton("Next");
add(next, BorderLayout.SOUTH);
next.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fadingPane.fadeOut();
}
});

pictures = new File("/Volumes/Disk02/Dropbox/MegaTokyo/thumnails").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
String name = pathname.getName().toLowerCase();
return name.endsWith(".jpg") || name.endsWith(".png");
}
});
nextPicture();
}

protected void nextPicture() {
index++;
if (index >= pictures.length) {
index = 0;
}
try {
BufferedImage img = ImageIO.read(pictures[index]);
label.setIcon(new ImageIcon(img));
} catch (IOException ex) {
ex.printStackTrace();
}
}

}

public interface FadeListener {
public void fadeDidStart(FadingPane panel);
public void fadeDidStop(FadingPane panel);
public void fadeOutDidComplete(FadingPane panel);
public void fadeInDidComplete(FadingPane panel);
}

public class FadingPane extends JPanel {

private float delta;
private float alpha = 1f;
private Timer timer;

private FadeListener fadeListener;

public FadingPane(FadeListener fadeListener) {
this.fadeListener = fadeListener;
// This is important, as we may not always be opaque
// and we don't want to stuff up the painting process
setOpaque(false);

timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
float alpha = getAlpha() + delta;
if (alpha < 0.001f) {
alpha = 0f;
timer.stop();
fadeListener.fadeOutDidComplete(FadingPane.this);
} else if (alpha >= 1.0f) {
alpha = 1.0f;
timer.stop();
fadeListener.fadeInDidComplete(FadingPane.this);
}
setAlpha(alpha);
}
});
}

public float getAlpha() {
return alpha;
}

public void setAlpha(float value) {
if (alpha != value) {
this.alpha = Math.min(1.0f, Math.max(0.0f, value));
repaint();
}
}

@Override
public void paint(Graphics g) {
// I don't normally recomamned overriding paint, but in this case,
// I want to affect EVERYTHING that might be added to this panel
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(getAlpha()));
super.paint(g2d);
g2d.dispose();
}

public void fadeIn() {
timer.stop();
fadeListener.fadeDidStop(FadingPane.this);
delta = 0.05f;
timer.restart();
fadeListener.fadeDidStart(FadingPane.this);
}

public void fadeOut() {
timer.stop();
fadeListener.fadeDidStop(FadingPane.this);
delta = -0.05f;
timer.restart();
fadeListener.fadeDidStart(FadingPane.this);
}

}

}

Using a thread to repaint() components

Primarily

You never add a ActionListener to either of your buttons, so nothing is responding when they are activated

Additionally

  1. The state management is all over the place. blueDraw and redDraw should be instance fields of ConcurrencyPanel.
  2. Don't update the state of the UI (or variables which the UI relies on) from within any paint method. Paint methods should paint the state, not change it. Updating the blueDraw and redDraw should be done in a specific method, which can be called when a update is required.

All of that leads me to believe you'd be better of using a Swing Timer

Conceptually...

You could do something like this...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class ConcurrencyRace {

//-----------------------------------------------------------------
// Creates and displays the main program frame.
//-----------------------------------------------------------------
public ConcurrencyRace() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Timer timer = new Timer(200, null);
JFrame frame = new JFrame();
frame.add(new ButtonPane(timer), BorderLayout.NORTH);
frame.add(new RacePane(timer));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

public static void main(String[] args) {
new ConcurrencyRace();
}

public class ButtonPane extends JPanel {

private JButton startRace = new JButton("Start The Race!");
private JButton stopRace = new JButton("Stop The Race!");

public ButtonPane(Timer timer) {
startRace.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
timer.start();
}
});

stopRace.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
}
});

setLayout(new GridBagLayout());
add(startRace);
add(stopRace);
}

}

private class RacePane extends JPanel {

private int blueDraw = 5, redDraw = 5;
private Random rn = new Random();

public RacePane(Timer timer) {
timer.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (updateState()) {
((Timer)e.getSource()).stop();
}
}
});
}

protected boolean updateState() {
blueDraw += rn.nextInt(10) + 1;
redDraw += rn.nextInt(10) + 1;
repaint();

return blueDraw >= getWidth() || redDraw >= getWidth();
}

@Override
public Dimension getPreferredSize() {
return new Dimension(600, 250);
}

@Override
public void paintComponent(Graphics page) {
System.out.println(">>");
super.paintComponent(page);
page.setColor(Color.blue);
page.fillRect(0, 80, blueDraw, 20);

page.setColor(Color.red);
page.fillRect(0, 120, redDraw, 20);
}
}
}

This maintains the Timer as the central concept, which is shared between the buttons and race panels.

I've not added support for generating notification of a winner, this would be done via a simple observer pattern passed to the RacePane

Overriding JButton paintComponent with transparency not showing back panel color

What you could do is instead of using clearRect(), clear the background with a completely transparent color.

g2.setColor(new Color(0,0,0,0));
g2.drawRect(0,0,width,height);

You still need to setOpaque(false) on the JButton so that it doesn't use the blue rollover color as the background once you hover over it once.

Edit: After seeing what you just posted, I think the problem is that the main frame isn't repainted.

Try:

SwingUtilities.getWindowAncestor(this).repaint();    

in the paint method to repaint the frame, that might fix the problem.

How to add non-Transparent components to Transparent JFrame in java 1.5?

Use JNA's WindowUtils.setWindowTransparent() method to start with a completely transparent window. Any pixels painted into that window will have their alpha component preserved.

JFrame f = ...
WindowUtils.setWindowTransparent(f, true);
// ensure JPanel content pane doesn't paint its (solid) background
f.getContentPane().setOpaque(false);
// Any other added components will be painted normally
f.getContentPane().add(new JButton("I'm opaque"));

This should deliver the desired results.

If you want your container to be semi-transparent, or other opacity combinations, you'll need to clarify your desired results.



Related Topics



Leave a reply



Submit