PDA

View Full Version : [JAVA-Swing] Hack: finestra trasparente


banryu79
11-07-2010, 20:36
Uno degli aggiornamenti introdotti al tempo del rilascio di Java SE 6u10 è la possibilità di avere finestre trasparenti (completamente o parzialmente) e di avere finestre di dimensione arbitraria (Shaped Windows).
[info qui (http://java.sun.com/developer/technicalArticles/GUI/translucent_shaped_windows/)]

Cosa si può fare, se l'applicazione gira su una piattaforma che non le supporta?

Una tecnica consiste nel "simulare" la trasparenza di una finestra disegnando in background gli stessi pixel che comparirebbero sotto la finestra, nello schermo dell'utente.
Per farlo bisogna creare un'immagine dello schermo-utente e disegnarla nella finestra traslando opportunamente le coordinate di riferimento, in modo che la porzione visibile dell'immagine si sovvrapponga correttamente con quanto mostrato a video (idea brutalmente rubata in giro sul web, ovviamente, così come il primo codice grezzo che poi ho esteso).

Posto due classi che ho scritto in questi due giorni, che realizzano una finestra trasparente con questa tecnica, che mostra lo sfondo del desktop con un leggero effetto di blur e un colore semi-trasparente aggiunto sopra, per dare l'impressione di osservare in trasparenza attraverso un vetro (o almeno, questa era l'intenzione :D)

Ecco un'immagine che ne mostra l'effetto, sul mio pc:
http://www.freeimagehosting.net/uploads/5889ab605b.jpg (http://www.freeimagehosting.net/)

A seguire il codice delle due classi implementate:
- JTransparentComponent, estensione di java.swing.JComponent
- JGhostFrame, estensione di java.swing.JFrame

JTransparentComponent gestisce l'effetto di trasparenza su se stesso, ed è pensato per essere usato come 'content pane' di un frame.

JGhostFrame invece realizza un frame che usa un JTransparentComponent come suo 'content pane' al posto di quello standard (un normale java.awt.Container)

Limiti attuali di JGhostPane:
- Non potendo rendere trasparente la 'title bar' di una finestra un JGhostFrame è sempre un JFrame 'undecorated' che gestisce e implementa la title bar internamente.
- Ha un supporto limitato per la trasparenza della 'menu bar' e di tutti i 'bottoni' (Dipende dal LnF)
- Non supporta componenti 'heavyweight', tipici di AWT, ma solo quelli 'lightweight' di Swing.


P.S.:
Commenti, critiche e quant'altro sono ben accetti.
Modifiche all'implementazione e alle idee anche.
:)

banryu79
11-07-2010, 20:39
Riporto per comodità di lettura il commento alla classe:

/**
* This class is a Swing container that paint itself with an image of the screen taken
* with java.awt.Robot, updated at appropriate times, to mimic a transparent background.
* <p>
* This class should be used as a substitute for the default content pane of a frame.
* In Swing the default content pane is a simple intermediate container that inherits from
* JComponent, and that uses a BorderLayout as its layout manager, so this class honors
* this fact.
* <p>
* To keep the background image updated this class add itself to the the given frame as a
* component listener and window focus event listener.
* <p>
* This class could also be used as a fake transparent background for an undecorated frame
* that shows images instead of components (think of an audio player user interface).<br>
* If the image used support transparency then the screen snapshot used as background will
* show through.
*
* @author francesco
*/


package transparentbackground;

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

/**
* This class is a Swing container that paint itself with an image of the screen taken
* with java.awt.Robot, updated at appropriate times, to mimic a transparent background.
* <p>
* This class should be used as a substitute for the default content pane of a frame.
* In Swing the default content pane is a simple intermediate container that inherits from
* JComponent, and that uses a BorderLayout as its layout manager, so this class honors
* this fact.
* <p>
* To keep the background image updated this class add itself to the the given frame as a
* component listener and window focus event listener.
* <p>
* This class could also be used as a fake transparent background for an undecorated frame
* that shows images instead of components (think of an audio player user interface).<br>
* If the image used support transparency then the screen snapshot used as background will
* show through.
*
* @author francesco
*/
public class JTransparentComponent extends JComponent
implements ComponentListener, WindowFocusListener, WindowListener//, Runnable
{
/**
* Transparency value for the background-color painted above the screenshot.<br>
* It is used to mimic the effect of looking it through colored glass.
*/
public static int backgroundTransparency = 192;

private final Robot ROBOT;
private final Rectangle SCREEN_RECT;
private final ConvolveOp BLUR_OP; // used to blur ligthly the screenshot
private final Color BACKGROUND_COLOR;

private JFrame frame; // the decorated frame, needed for position calculation
private Image screenSnapshot; // used to mimic transparent background
private long lastUpdate;
private boolean refreshRequested;
private boolean frameIsNotClosing;


/**
* Create a new transparent component and bind it to the given frame.
* @param frame the JFrame instance for which to bind this component.
*/
public JTransparentComponent(JFrame frame) {
// this component is intended to be used as a replacement for the
// content pane of a frame, so it is appropriate to init its layout
// manager to BorderLayout.
this.setLayout(new BorderLayout());

this.frame = frame;

try {
ROBOT = new Robot();
} catch (AWTException ex) {
throw new HeadlessException("Cannot create java.awt Robot object on \"headless\" platform.");
}

SCREEN_RECT = getTotalScreenBounds();
BLUR_OP = createBlurConvolution();
BACKGROUND_COLOR = new Color(255, 255, 255, backgroundTransparency);

takeScreenSnapshot();

// We track some component events and window events that force the
// updating of the screenshot.
// To show constatly the correct portion of the screenshot at current
// component location we respond to: componentMoved, componentResized,
// componentShown with a repaint.
frame.addComponentListener(this);

// We track focus gained/lost events because we cannot know what the user is
// doing with other windows on the system (moving them and so obscuring/reveal
// again this component and the frame associated) or if the user is changing
// desktop settings, so it is better to make a request for taking a fresh new
// snapshot of the screen.
frame.addWindowFocusListener(this);

// We track the window opened/closing events to start/stop the thread responsible
// for the correct updating of the screenshot.
frame.addWindowListener(this);
}

// Used to init the convolution used for the blur effect on the screenshot.
private ConvolveOp createBlurConvolution() {
float[] lightBlur = { .07f, .13f, .07f,
.13f, .20f, .13f,
.07f, .13f, .07f };
return new ConvolveOp(new Kernel(3, 3, lightBlur));
}

// Calculate the screen dimension used for taking the screenshot
private Rectangle getTotalScreenBounds() {
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();

// take account of multiple, side-by-side, screens
int w = 0;
for (GraphicsDevice d : ge.getScreenDevices())
w += ge.getMaximumWindowBounds().width;

int h = ge.getMaximumWindowBounds().height;
return new Rectangle(0, 0, w, h);
}

private void takeScreenSnapshot() {
BufferedImage buf = ROBOT.createScreenCapture(SCREEN_RECT);
screenSnapshot = BLUR_OP.filter(buf, null);
}

// request a refresh
private void refresh() {
if (frame.isVisible()) {
repaint();
refreshRequested = true;
lastUpdate = System.currentTimeMillis();
}
}

@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Point pos = this.getLocationOnScreen();

// draw convolved background image
g2.drawImage(screenSnapshot, -pos.x, -pos.y, null);

// add semi-transparent color
g2.setColor(BACKGROUND_COLOR);
g2.fillRect(0, 0, getWidth(), getHeight());
}

public void componentHidden(ComponentEvent e) {
// do nothing
}

public void componentMoved(ComponentEvent e) {
repaint();
}

public void componentResized(ComponentEvent e) {
repaint();
}

public void componentShown(ComponentEvent e) {
repaint();
}

public void windowGainedFocus(WindowEvent e) {
refresh();
}

public void windowLostFocus(WindowEvent e) {
//refresh();
}

/**
* Encapsulate calls on the EDT to setLocation.
* Used to move the frame associated with this
* component out of visible desktop area before
* taking a screenshot.
*/
class CallSetLocation implements Runnable
{
final int X;
final int Y;

CallSetLocation(Point p) {
this(p.x, p.y);
}

CallSetLocation(int x, int y) {
X = x;
Y = y;
}

public void run() {
frame.setLocation(X, Y);
}
}

// TASK: Improve ScreenshotUpdater
// Also all frame children of this 'frame' must be considered
// when moving out of desktop visible area at snapshot-time.

/**
* Used to encapsulate the task of taking a screenshot.
* It should always run out of the EDT, and need to syncrho/wait
* correctly with CallSetLocation, for taking snapshot correctly.
*/
class ScreenshotUpdater extends ComponentAdapter implements Runnable
{
CallSetLocation moveBack, moveOut;
boolean movedOutForScreenshot;

ScreenshotUpdater() {
frame.addComponentListener(this);
}

private void pauseThread(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ignored) {}
}

public void run() {
while (frameIsNotClosing) {
pauseThread(75);
long now = System.currentTimeMillis();
if (refreshRequested && ((now - lastUpdate) > 500)) {
if (frame.isVisible()) {
moveBack = new CallSetLocation(frame.getLocation());
moveOut = new CallSetLocation(-frame.getWidth(),
-frame.getHeight());
movedOutForScreenshot = true;
SwingUtilities.invokeLater(moveOut);
// from here:
// moveBack is then called in componentMoved.
}
lastUpdate = now;
refreshRequested = false;
}
}
}

@Override
public void componentMoved(ComponentEvent e) {
if (movedOutForScreenshot) {
movedOutForScreenshot = false;
takeScreenSnapshot();
moveBack.run(); // we already are on the EDT here.
}
}
}

public void windowOpened(WindowEvent e) {
lastUpdate = -10000000;
refreshRequested = true;
frameIsNotClosing = true;
ScreenshotUpdater updater = new ScreenshotUpdater();
new Thread(updater).start();
}

public void windowClosing(WindowEvent e) {
int closeOperation =
frame.getDefaultCloseOperation();

if (closeOperation == JFrame.DISPOSE_ON_CLOSE) {
frame.removeWindowListener(this);
frame.removeWindowFocusListener(this);
frame.removeComponentListener(this);
frameIsNotClosing = false;
frame = null;
}
}

public void windowClosed(WindowEvent e) {
// do nothing
}

public void windowIconified(WindowEvent e) {
// do nothing
}

public void windowDeiconified(WindowEvent e) {
// do nothing
}

public void windowActivated(WindowEvent e) {
// do nothing
}

public void windowDeactivated(WindowEvent e) {
// do nothing
}

}

banryu79
11-07-2010, 20:40
/**
* A sort of "transparent background" JFrame that:
* <p>
* - supports a "fake" transparent title bar<br>
* - give limited support for transparency to menu bar and buttons<br>
* - supports a "fake" transparent content pane (JTransparentComponent).<br>
* - can contain only lightweight components (no heavyweight).
* <p>
* It make this setting as non-opaque all added components to it's special content
* pane (a JTransparentComponent).
* <p>
* The frame itself is an undecorated JFrame: this turn of the visible title bar and
* the insets border commonly used to resize a frame. To overcome this limitation a
* FakeTitleArea container is added on the "north" portion of the JTransparentComponent
* installed as the content pane (it provide common functionality for the title bar and
* manages another small area below the title bar used for supporting a JMenuBar).
* A FakeClientArea is added to the "central" portion of the content pane, and all calls
* for adding components to the frame content pane are redirected to target it as the
* container.<br>
*
* @author francesco
*/


package transparentbackground;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
/**
* A sort of "transparent background" JFrame that:
* <p>
* - supports a "fake" transparent title bar<br>
* - give limited support for transparency to menu bar and buttons<br>
* - supports a "fake" transparent content pane (JTransparentComponent).<br>
* - can contain only lightweight components (no heavyweight).
* <p>
* It make this setting as non-opaque all added components to it's special content
* pane (a JTransparentComponent).
* <p>
* The frame itself is an undecorated JFrame: this turn of the visible title bar and
* the insets border commonly used to resize a frame. To overcome this limitation a
* FakeTitleArea container is added on the "north" portion of the JTransparentComponent
* installed as the content pane (it provide common functionality for the title bar and
* manages another small area below the title bar used for supporting a JMenuBar).
* A FakeClientArea is added to the "central" portion of the content pane, and all calls
* for adding components to the frame content pane are redirected to target it as the
* container.<br>
*
* @author francesco
*/
public class JGhostFrame extends JFrame
{
// TASK: Usage Examples
// 1- usage of JGhostFrame as "decorated" frame (DONE)
// 2- usage of JGhostFrame as "undecorated", with image that support alpha channels
// 3- try fancy use of JTransparentComponent as one the subcomponents used in an ordinary JFrame.

/**
* Set the global background transparency value for all frames created.
* @param transparency must be in the range 0-255 inclusive.
*/
public static void setTransparency(int transparency) {
if (transparency >= 0 && transparency <= 255)
JTransparentComponent.backgroundTransparency = transparency;
}

private static final Border STD_BORDER =
BorderFactory.createLineBorder(Color.black, 1);


private final JTransparentComponent BACKGROUND;
private final FakeTitleArea TITLE_AREA;
private final FakeClientArea CLIENT_AREA;
private final OpacityManager OPACITY_MANAGER;

// Overrided properties
boolean undecorated;


public JGhostFrame() {
super();
super.setUndecorated(true);

// Change our content pane to JTransparentComponent
BACKGROUND = new JTransparentComponent(this);
BACKGROUND.setBorder(STD_BORDER);
this.setContentPane(BACKGROUND);

// Build fake title bar
TITLE_AREA = new FakeTitleArea(this);
BACKGROUND.add("North", TITLE_AREA);

// Build fake client area
CLIENT_AREA = new FakeClientArea(this);
BACKGROUND.add("Center", CLIENT_AREA);

// initialize the Opacity Manager
OPACITY_MANAGER =
new OpacityManager(JTransparentComponent.backgroundTransparency);
}

/**
* Wrapped: the menubar is managed by the FakeTitleArea instead of
* the standard JFrame's RootPane.
*/
@Override
public void setJMenuBar(JMenuBar menubar) {
TITLE_AREA.setJMenuBar(menubar);
}

/**
* Wrapped: the menubar is managed by the FakeTitleArea instead of
* the standard JFrame's RootPane.
*/
@Override
public JMenuBar getJMenuBar() {
return TITLE_AREA.getJMenuBar();
}

/**
* Wrapped: each JComponent added must be setted non-opaque, and
* all children it contains. Only lightweight components.
*/
@Override
public Component add(Component comp) {
OPACITY_MANAGER.makeNonOpaque(comp);
CLIENT_AREA.add(comp);
return comp;
}

/**
* Wrapped: each JComponent added must be setted non-opaque, and
* all children it contains. Only lightweight components.
*/
@Override
public Component add(String name, Component comp) {
OPACITY_MANAGER.makeNonOpaque(comp);
CLIENT_AREA.add(name, comp);
return comp;
}

/**
* Wrapped: each JComponent added must be setted non-opaque, and
* all children it contains. Only lightweight components.
*/
@Override
public Component add(Component comp, int index) {
OPACITY_MANAGER.makeNonOpaque(comp);
CLIENT_AREA.add(comp, index);
return comp;
}

/**
* Wrapped: each JComponent added must be setted non-opaque, and
* all children it contains. Only lightweight components.
*/
@Override
public void add(Component comp, Object constraints) {
if (CLIENT_AREA != null) {
OPACITY_MANAGER.makeNonOpaque(comp);
CLIENT_AREA.add(comp, constraints);
}
else
super.add(comp, constraints);
}

/**
* Wrapped: each JComponent added must be setted non-opaque, and
* all children it contains. Only lightweight components.
*/
@Override
public void add(Component comp, Object constraints, int index) {
if (CLIENT_AREA != null) {
OPACITY_MANAGER.makeNonOpaque(comp);
CLIENT_AREA.add(comp, constraints, index);
}
else
super.add(comp, constraints, index);
}

/**
* Wrapped: the menubar is managed by the FakeTitleArea instead of
* the standard JFrame's RootPane.
*/
@Override
public void setUndecorated(boolean undecorate) {
if (undecorate) {
BACKGROUND.remove(TITLE_AREA);
this.validate();
}
else if (isUndecorated()) {
BACKGROUND.add("North", TITLE_AREA);
this.validate();
}

this.undecorated = undecorate;
}

/**
* Wrapped: the menubar is managed by the FakeTitleArea instead of
* the standard JFrame's RootPane.
*/
@Override
public boolean isUndecorated() {
return this.undecorated;
}

/**
* Wrapped: title bar is "faked", so JGhostFrame must explicitly
* manage title updates.
*/
@Override
public void setTitle(String title) {
super.setTitle(title);
TITLE_AREA.updateTitle(title);
}


class FakeTitleArea extends JPanel
{
private final JGhostFrame T_FRAME;

// North area is for title box; South area is for menubar panel.
private final Box TITLE_BOX;
private final JLabel LB_TITLE;

private JMenuBar menubar;

FakeTitleArea(JGhostFrame f) {
super();
setOpaque(false);
setLayout(new BorderLayout());

T_FRAME = f;

TITLE_BOX = Box.createHorizontalBox();
TITLE_BOX.setOpaque(false);
TITLE_BOX.setBorder(JGhostFrame.STD_BORDER);
MouseAdapter moveHandler = new MouseAdapter()
{
Point last = null;
Point offset = new Point();
@Override public void mouseDragged(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
Point p = e.getLocationOnScreen();
if (last != null) {
offset.x = p.x - last.x;
offset.y = p.y - last.y;
Point loc = T_FRAME.getLocationOnScreen();
T_FRAME.setLocation(loc.x + offset.x, loc.y + offset.y);
}
last = p;
}
}
@Override public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e))
last = null;
}
};
TITLE_BOX.addMouseMotionListener(moveHandler);
TITLE_BOX.addMouseListener(moveHandler);

JLabel resizer = new JLabel("[o]");
resizer.setToolTipText("resize");
MouseAdapter resizeHandler = new MouseAdapter()
{
Point last = null;
Point offset = new Point();
@Override public void mouseDragged(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
Point p = e.getPoint();
if (last != null && T_FRAME.isVisible()) {
offset.x = p.x - last.x;
offset.y = p.y - last.y;
Dimension size = T_FRAME.getSize();
int newX = size.width + offset.x;
int newY = size.height + offset.y;
T_FRAME.setSize(newX, newY);
}
last = p;
}
}
@Override public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e))
last = null;
}
};
resizer.addMouseMotionListener(resizeHandler);
resizer.addMouseListener(resizeHandler);

JLabel iconizer = new JLabel("[-]");
iconizer.setToolTipText("iconize");
iconizer.addMouseListener(new MouseAdapter() {
@Override public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e))
T_FRAME.setExtendedState(JFrame.ICONIFIED);
}
});

JLabel maximizer = new JLabel("[#]");
maximizer.setToolTipText("maximize");
maximizer.addMouseListener(new MouseAdapter() {
Rectangle bounds = null;
@Override public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
int state = T_FRAME.getExtendedState();
if ((state & JFrame.MAXIMIZED_BOTH) != JFrame.MAXIMIZED_BOTH) {
bounds = T_FRAME.getBounds(bounds);
T_FRAME.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
else
T_FRAME.setBounds(bounds);
}
}
});

JLabel quitter = new JLabel("[x]");
quitter.setToolTipText("close");
quitter.addMouseListener(new MouseAdapter() {
@Override public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
WindowEvent close = new WindowEvent(T_FRAME, WindowEvent.WINDOW_CLOSING);
T_FRAME.dispatchEvent(close);
}
}
});

LB_TITLE = new JLabel();
LB_TITLE.setOpaque(false);

TITLE_BOX.add(Box.createHorizontalStrut(1));
TITLE_BOX.add(resizer);
TITLE_BOX.add(Box.createHorizontalGlue());
TITLE_BOX.add(LB_TITLE);
TITLE_BOX.add(Box.createHorizontalGlue());
TITLE_BOX.add(iconizer);
TITLE_BOX.add(Box.createHorizontalStrut(3));
TITLE_BOX.add(maximizer);
TITLE_BOX.add(Box.createHorizontalStrut(3));
TITLE_BOX.add(quitter);
TITLE_BOX.add(Box.createHorizontalStrut(1));

this.add(TITLE_BOX, BorderLayout.NORTH);
}

private void updateTitle(String title) {
LB_TITLE.setText(title);
}

/**
* Wrapped: any Component added must be setted non-opaque, and
* all children it contains, so only JComponent and its subclasses
* are allowed.
*/
public void setJMenuBar(JMenuBar menubar) {
if (menubar != null) {
OPACITY_MANAGER.makeNonOpaque(menubar);
menubar.setBorder(JGhostFrame.STD_BORDER);
this.menubar = menubar;
this.add(menubar, BorderLayout.SOUTH);
}
}

public JMenuBar getJMenuBar() {
return this.menubar;
}
}


class FakeClientArea extends JPanel
{
private final JGhostFrame T_FRAME;

FakeClientArea(JGhostFrame f) {
T_FRAME = f;
setOpaque(false);
setBorder(JGhostFrame.STD_BORDER);
// mimic a frame content pane
setLayout(new BorderLayout());
}
}

}



Ora JGhostFrame fa uso di OpacityManager per gestire le questioni relative alla trasparenza quando gli viene aggiunto (metodi add) un child component:

package transparentbackground;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.util.Stack;
import javax.swing.JComponent;

/**
* This class is an helper used during add() operation on a "ghost frame", for validation
* and opacity setting.
* <p>
* It performs validation on the component to be added because only Swing "lightweight"
* components have the setOpacity() method that enables translucency for their background.
*
* @author francesco
*/
public class OpacityManager
{
private static final String MSG_INVALID_COMPONENT =
"Only \"lightweight\" components could be added.";


/**
* utility stack of components that allow easy insertion (pushes)
* of all the children of a container
*/
private class ComponentStack
{
private Stack<Component> stack =
new Stack<Component>();

public void push(Component comp) {
stack.push(comp);
}

public Component pop() {
return stack.pop();
}

public Component peek() {
return stack.peek();
}

public boolean notEmpty() {
return !stack.empty();
}

public void pushChildren(Container container) {
for (Component c : container.getComponents())
stack.push(c);
}

public void clear() {
stack.clear();
}
}

private final ComponentStack STACK;
private final int OPACITY;

/**
* Construct an OpacityManager for a given transparency
* @param alpha the alpha value for transparency (0-255)
*/
public OpacityManager(int alpha) {
STACK = new ComponentStack();
OPACITY = alpha;
}

/**
* Validate and set the opacity for a given Component and all
* its children that have to be added to a "ghost frame"
* @param comp the candidate component
*/
public void makeNonOpaque(Component comp) {
STACK.clear();
STACK.push(comp);

while (STACK.notEmpty()) {
Component popped = STACK.pop();
//System.out.println(popped.getClass().getName());

if (popped instanceof JComponent) {
JComponent jComp = (JComponent) popped;
STACK.pushChildren(jComp);

// make non-opaque:
jComp.setOpaque(false);
// set background color alpha channel transparency
// to the same value used in the color painted above
// the screenshot. This is useful for make some
// components, like buttons, truly transparent
// (in some LnF, for example: MetalLookAndFeel)
changeBackgroundOpacity(jComp, OPACITY);
}
else if (popped instanceof Container) {
Container cont = (Container) popped;
STACK.pushChildren(cont);

String className = cont.getClass().getName();
if (! className.startsWith("javax.swing."))
throw new IllegalArgumentException(MSG_INVALID_COMPONENT);
}
else
throw new IllegalArgumentException(MSG_INVALID_COMPONENT);
}
}

private void changeBackgroundOpacity(Component comp, int alpha) {
Color col = comp.getBackground();
if (col != null)
comp.setBackground(
new Color(col.getRed(), col.getGreen(), col.getBlue(), alpha));
}
}

banryu79
11-07-2010, 20:45
E qui sotto tre classi che ne mostrano l'uso (l'esempio + due utility).

DecoratedGhostFrameExample:

package transparentbackground.example;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import temp.JTextForm;
import temp.LookNFeelSetter;
import transparentbackground.JGhostFrame;

/**
* Show a 'decoreted' JGhostFrame
*
* @author francesco
*/
public class DecoratedGhostFrameExample
{
public static void main(String[] args) throws Exception {

LookNFeelSetter.chooseByIndex(args);

// create our JGhostFrame, that internally use
// JTransparentComponent as its content pane:
JGhostFrame.setTransparency(192);
final JGhostFrame FRAME = new JGhostFrame();
FRAME.setTitle("GhostFrame Demo");
FRAME.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

// add a menu bar:
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
fileMenu.add(new JMenuItem("New..."));
fileMenu.addSeparator();
fileMenu.add(new JMenuItem("Open"));
fileMenu.addSeparator();
fileMenu.add(new JMenuItem("Save"));
fileMenu.add(new JMenuItem("Save as..."));
menuBar.add(fileMenu);
JMenu editMenu = new JMenu("Edit");
editMenu.add(new JMenuItem("Copy"));
editMenu.add(new JMenuItem("Cut"));
editMenu.add(new JMenuItem("Paste"));
editMenu.addSeparator();
editMenu.add(new JMenuItem("Find"));
menuBar.add(editMenu);
JMenu viewMenu = new JMenu("View");
viewMenu.add(new JMenuItem("Full screen"));
menuBar.add(viewMenu);
JMenu helpMenu = new JMenu("Help");
helpMenu.add(new JMenuItem("Help Contents"));
helpMenu.add(new JMenuItem("About"));
menuBar.add(Box.createHorizontalGlue());
menuBar.add(helpMenu);
//menuBar.add(new JSeparator(SwingConstants.VERTICAL));
FRAME.setJMenuBar(menuBar);

// add a form in the center:
Box title = Box.createHorizontalBox();
title.add(Box.createHorizontalStrut(3));
title.add(new JLabel("Please, fill in this form:"), JLabel.CENTER);
title.add(Box.createHorizontalGlue());
FRAME.add(title, BorderLayout.NORTH);

JTextForm.Builder builder = new JTextForm.Builder();
builder.setLabels("First Name", "Middle Initial", "Last Name", "Age")
.setTips("insert your name", "insert your middle name initial",
"insert your last name", "insert your age")
.setMemonics('F', 'M', 'L', 'A')
.setWidths(15, 1, 15, 3);
final JTextForm FORM = builder.create();
FRAME.add(FORM, BorderLayout.CENTER);

// abb buttons in the south:
JPanel buttonPan = new JPanel(new FlowLayout(FlowLayout.RIGHT));

String[] values = {"Example", "of", "a", "checkbox"};
JComboBox combo = new JComboBox(values);
buttonPan.add(combo);

final JButton bMenuR = new JButton("Show/hide Menubar");
bMenuR.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuBar bar = FRAME.getJMenuBar();
bar.setVisible(!bar.isVisible());
}
});
buttonPan.add(bMenuR);

final JButton bUndecor = new JButton("Undecorate");
JButton bDecor = new JButton("Decorate");
ActionListener decorators = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButton but = (JButton) e.getSource();
FRAME.setUndecorated(but.equals(bUndecor));
}
};
bUndecor.addActionListener(decorators);
bDecor.addActionListener(decorators);
buttonPan.add(bUndecor);
buttonPan.add(bDecor);

FRAME.add(buttonPan, BorderLayout.SOUTH);


// show it:
SwingUtilities.invokeLater(new Runnable() {
@Override public void run() {
FRAME.pack();
FRAME.setVisible(true);
}
});
}
}



LookNFeelSetter:

package temp;

import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

/**
* Utility to set up the LnF for the application from first argument passed on the command line.
* The argument must be an integer value that represent the index value of the platform's currently
* installed Look'n'Feels.
*
* @author francesco
*/
public class LookNFeelSetter
{
public static void chooseByIndex(String[] args) {

String msgError =
"Usage:\njava JGhostFrame [integer]\nYou must pass a valid integer argument";

if (args.length < 1) {
throw new RuntimeException(msgError);
}

int argValue;
try {
argValue = Integer.parseInt(args[0]);
} catch (NumberFormatException ex) {
throw new RuntimeException(msgError, ex);
}

String lnfClassName = null;
LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
try {
lnfClassName = infos[argValue].getClassName();
System.out.println("Using "+lnfClassName);
} catch (ArrayIndexOutOfBoundsException ex) {
System.err.println("Argument value too big ("+argValue+").\nMax allowed is "+infos.length);
throw new RuntimeException(msgError, ex);
}

try {
UIManager.setLookAndFeel(lnfClassName);
} catch (Exception ex) {
System.err.println("Look'n'Feel installation failed.");
System.exit(1);
}
}
}



JTextForm:

package temp;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
* A simple label/field form panel
*
* @author francesco
*/
public class JTextForm extends JPanel
{
static String[] labels = {"Label"};
static char[] mnemonics = {'L'};
static int[] widths = {15};
static String[] tips = {"this is a tip"};

public static class Builder
{
public JTextForm.Builder setLabels(String... ls) {
labels = ls; return this;
}

public JTextForm.Builder setMemonics(char... ms) {
mnemonics = ms; return this;
}

public JTextForm.Builder setWidths(int... ws) {
widths = ws; return this;
}

public JTextForm.Builder setTips(String... ts) {
tips = ts; return this;
}

public JTextForm create() {
return new JTextForm();
}
}

private JTextField[] fields;

public JTextForm() {
super(new BorderLayout());

JPanel labelPan = new JPanel(new GridLayout(labels.length, 1));
JPanel fieldPan = new JPanel(new GridLayout(labels.length, 1));
this.add(labelPan, BorderLayout.WEST);
this.add(fieldPan, BorderLayout.CENTER);

fields = new JTextField[labels.length];
for (int i=0; i<labels.length; i++) {
fields[i] = new JTextField();
if (i < tips.length) fields[i].setToolTipText(tips[i]);
if (i < widths.length) fields[i].setColumns(widths[i]);

JLabel l = new JLabel(labels[i], JLabel.RIGHT);
l.setLabelFor(fields[i]);
if (i < mnemonics.length) l.setDisplayedMnemonic(mnemonics[i]);
labelPan.add(l);

JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
p.add(fields[i]);
fieldPan.add(p);
}
}

public String getText(int i) {
return fields[i].getText();
}
}

Ryuzaki_Eru
12-07-2010, 00:41
Complimenti :)

*andre*
12-07-2010, 12:17
ottimo davvero complimenti :D

ally
12-07-2010, 16:19
...decisamente interessante...

...ciao Andrea...

MEMon
12-07-2010, 21:43
Ciao, interessante, io avevo adottato la stessa tecnica con Labview quando ero piccino :D

Ti volevo chiedere, col trascinamento come si comporta, è veloce?

DanieleC88
13-07-2010, 00:55
Bel lavoro! :mano:

banryu79
13-07-2010, 09:34
Ti volevo chiedere, col trascinamento come si comporta, è veloce?

Sì, trascinamento/resize della window non è affatto un problema, la "fregatura" con questo sistema è dovuta alla necessità di far momentaneamente sparire la finestra stessa dall'area visibile del desktop, ogni volta che si deve aggiornare lo snapshot dello screen.

E' questione di un attimo, ma quell'attimo l'utente lo percepisce come una rapida successione scomparsa-ricomparsa della finestra.
(Gli approcci sono due: usare setVisible oppure traslare la location della finestra in una posizione logica non compresa nell'area visibile del desktop, io ho scelto la seconda).
Questo aspetto sarebbe la parte "da limare" e ho intenzione di investigare un po' per vedere se trovo un 'escamotage'...

L'approccio comunque era interessante prima che Swing introducesse il supporto alle Translucent & Shaped Windows; e in particolare era interessante nella sua versione più semplice quando uno vuole usare come finestra d'interfaccia un'immagine particolare che non è rettangolare (ad esempio, nell'immagine del mio desktop postata, la finestra d'interfaccia del mio cellulare).

Io ho solo voluto provare a vedere se riuscivo a estenderlo in modo tale da realizzare una classe che dal punto di vista del codice client fosse in tutto e per tutto paragonabile a JFrame.

P.S.:
Comunque puoi povarlo di persona: devi solo crearti un nuovo progetto nella tua IDE preferita che supporti Java, crearti un package di nome 'transparentbackground' e copia-incollare le prime due classi postate.