关于Swing实现透明窗体的。我也很喜欢,收藏!
2006-12-12 21:53
387 查看
Editor's note: The following example from Swing Hacks is one of the book's most visually daring hacks--mimicking the arbitrarily shaped window you might see in an native MP3 player skin. The hack here is necessitated by the fact that Java doesn't support non-rectangular windows, so the only option to make this work is for the Java window to be aware of what's under it, and to handle the imaging of areas within the window's rectangle but not within its arbitrary shape. Read on for how this is accomplished.
Create translucent and shaped windows, while avoiding native code, with clever use of a screenshot.
One of the most commonly requested Swing features is transparent windows. Also called shaped windows, these are windows that have transparent portions, allowing the desktop background and other programs to shine through. Java doesn't provide any way of creating transparent windows without using the Java Native Interface (JNI) (and even then the native platform must support transparency as well), but that's not going to stop us. We can cheat using one of my favorite techniques, the screenshot.
The process of faking a transparent window is basically:
Take a screenshot before the window is shown.
Use that screenshot as the background of the window.
Adjust the position so that the screenshot and the real screen line up, creating the illusion of transparency.
This is the easy part. The hard part is updating the screenshot when the window moves or changes.
To start off, create a
public class TransparentBackground extends Jcomponent {
private JFrame frame;
private Image background;
public TransparentBackground(JFrame frame) {
this .frame = frame;
updateBackground( );
}
public void updateBackground( ) {
try {
Robot rbt = new Robot( );
Toolkit tk = Toolkit.getDefaultToolkit( );
Dimension dim = tk.getScreenSize( );
background = rbt.createScreenCapture(
new Rectangle( 0 , 0 ,( int )dim.getWidth( ),
( int )dim.getHeight( )));
} catch (Exception ex) {
p(ex.toString( ));
ex.printStackTrace( );
}
}
public void paintComponent(Graphics g) {
Point pos = this .getLocationOnScreen( );
Point offset = new Point( - pos.x, - pos.y);
g.drawImage(background,offset.x,offset.y, null );
}
First, the constructor saves a reference to the parent
You can run this with a simple
public static void main(String[] args) {
JFrame frame = new JFrame( " Transparent Window " );
TransparentBackground bg = new TransparentBackground(frame);
bg.setLayout( new BorderLayout( ));
JButton button = new JButton( " This is a button " );
bg.add( " North " ,button);
JLabel label = new JLabel( " This is a label " );
bg.add( " South " ,label);
frame.getContentPane( ).add( " Center " ,bg);
frame.pack( );
frame.setSize( 150 , 100 );
frame.show( );
}
The code produces a window that looks like Figure 6-1.
The code is pretty simple, but it has two big flaws. First, if the window is moved, the background won't be refreshed automatically.
You really don't want to update the screenshot often, though, because that involves hiding the window, taking a new screenshot, and then reshowing the window—all of which is disconcerting to the user. Actually detecting when the rest of the desktop changes is almost impossible, but most changes happen when the foreground window changes focus or moves. If you accept this idea (and I do), then you can watch for those events and only update the screenshot when that happens:
public class TransparentBackground extends JComponent
implements ComponentListener, WindowFocusListener,
Runnable {
private JFrame frame;
private Image background;
private long lastupdate = 0 ;
public boolean refreshRequested = true ;
public TransparentBackground(JFrame frame) {
this .frame = frame;
updateBackground( );
frame.addComponentListener( this );
frame.addWindowFocusListener( this );
new Thread( this ).start( );
}
public void componentShown(ComponentEvent evt) { repaint( ); }
public void componentResized(ComponentEvent evt) { repaint( ); }
public void componentMoved(ComponentEvent evt) { repaint( ); }
public void componentHidden(ComponentEvent evt) { }
public void windowGainedFocus(WindowEvent evt) { refresh( ); }
public void windowLostFocus(WindowEvent evt) { refresh( ); }
First, make the panel,
The implementation of
public void refresh( ) {
if (frame.isVisible( )) {
repaint( );
refreshRequested = true ;
lastupdate = new Date( ).getTime( );
}
}
public void run( ) {
try {
while ( true ) {
Thread.sleep( 250 );
long now = new Date( ).getTime( );
if (refreshRequested &&
((now - lastupdate) > 1000 )) {
if (frame.isVisible( )) {
Point location = frame.getLocation( );
frame.hide( );
updateBackground( );
frame.show( );
frame.setLocation(location);
refresh( );
}
lastupdate = now;
refreshRequested = false ;
}
}
} catch (Exception ex) {
p(ex.toString( ));
ex.printStackTrace( );
}
}
The
So, why all of this rigmarole about using a thread to control refreshing? One word: recursion. The event handlers could simply call
Additionally, any user action that would change the screen will probably create lots of events, not just one. It's just the last event that should trigger
Another annoyance is that the window still has its border, which sort of ruins the effect of having a transparent background. Unfortunately, removing the borders with
Here's a simple test program to put this into action:
public static void main(String[] args) {
JFrame frame = new JFrame( " Transparent Window " );
frame.setUndecorated( true );
TransparentBackground bg = new TransparentBackground(frame);
bg.snapBackground( );
bg.setLayout( new BorderLayout( ));
JPanel panel = new JPanel( ) {
public void paintComponent(Graphics g) {
g.setColor(Color.blue);
Image img = new ImageIcon( " mp3.png " ).getImage( );
g.drawImage(img, 0 , 0 , null );
}
};
panel.setOpaque( false );
bg.add( " Center " ,panel);
frame.getContentPane( ).add( " Center " ,bg);
frame.pack( );
frame.setSize( 200 , 200 );
frame.setLocation( 500 , 500 );
frame.show( );
}
The code creates a faux MP3 player interface using a
Figure 6-2. Template for an MP3 player
Figure 6-3. Running the MP3 player
Create translucent and shaped windows, while avoiding native code, with clever use of a screenshot.
One of the most commonly requested Swing features is transparent windows. Also called shaped windows, these are windows that have transparent portions, allowing the desktop background and other programs to shine through. Java doesn't provide any way of creating transparent windows without using the Java Native Interface (JNI) (and even then the native platform must support transparency as well), but that's not going to stop us. We can cheat using one of my favorite techniques, the screenshot.
The process of faking a transparent window is basically:
Take a screenshot before the window is shown.
Use that screenshot as the background of the window.
Adjust the position so that the screenshot and the real screen line up, creating the illusion of transparency.
This is the easy part. The hard part is updating the screenshot when the window moves or changes.
To start off, create a
JPanelsubclass that can capture the screen and paint it as the background, as shown in Example 6-1
Example 6-1. A transparent background component
public class TransparentBackground extends Jcomponent {
private JFrame frame;
private Image background;
public TransparentBackground(JFrame frame) {
this .frame = frame;
updateBackground( );
}
public void updateBackground( ) {
try {
Robot rbt = new Robot( );
Toolkit tk = Toolkit.getDefaultToolkit( );
Dimension dim = tk.getScreenSize( );
background = rbt.createScreenCapture(
new Rectangle( 0 , 0 ,( int )dim.getWidth( ),
( int )dim.getHeight( )));
} catch (Exception ex) {
p(ex.toString( ));
ex.printStackTrace( );
}
}
public void paintComponent(Graphics g) {
Point pos = this .getLocationOnScreen( );
Point offset = new Point( - pos.x, - pos.y);
g.drawImage(background,offset.x,offset.y, null );
}
First, the constructor saves a reference to the parent
JFrame; then it calls
updateBackground( ), which captures the entire screen using
java.awt.Robot.
createScreenCapture( ), and saves the capture in the
backgroundvariable.
paintComponent( ) gets the panel's absolute position on screen and then fills the panel with the background image, shifted to account for the panel's location. This makes the fake background image line up with the real background, giving the appearance of transparency.
You can run this with a simple
main( ) method, dropping a few components onto the panel and putting it into a frame:
public static void main(String[] args) {
JFrame frame = new JFrame( " Transparent Window " );
TransparentBackground bg = new TransparentBackground(frame);
bg.setLayout( new BorderLayout( ));
JButton button = new JButton( " This is a button " );
bg.add( " North " ,button);
JLabel label = new JLabel( " This is a label " );
bg.add( " South " ,label);
frame.getContentPane( ).add( " Center " ,bg);
frame.pack( );
frame.setSize( 150 , 100 );
frame.show( );
}
The code produces a window that looks like Figure 6-1.
The code is pretty simple, but it has two big flaws. First, if the window is moved, the background won't be refreshed automatically.
paintComponent( ) only gets called when the user resizes the window. Second, if the screen ever changes, it won't match up with the background anymore.
You really don't want to update the screenshot often, though, because that involves hiding the window, taking a new screenshot, and then reshowing the window—all of which is disconcerting to the user. Actually detecting when the rest of the desktop changes is almost impossible, but most changes happen when the foreground window changes focus or moves. If you accept this idea (and I do), then you can watch for those events and only update the screenshot when that happens:
public class TransparentBackground extends JComponent
implements ComponentListener, WindowFocusListener,
Runnable {
private JFrame frame;
private Image background;
private long lastupdate = 0 ;
public boolean refreshRequested = true ;
public TransparentBackground(JFrame frame) {
this .frame = frame;
updateBackground( );
frame.addComponentListener( this );
frame.addWindowFocusListener( this );
new Thread( this ).start( );
}
public void componentShown(ComponentEvent evt) { repaint( ); }
public void componentResized(ComponentEvent evt) { repaint( ); }
public void componentMoved(ComponentEvent evt) { repaint( ); }
public void componentHidden(ComponentEvent evt) { }
public void windowGainedFocus(WindowEvent evt) { refresh( ); }
public void windowLostFocus(WindowEvent evt) { refresh( ); }
First, make the panel,
TransparentWindow, implement
ComponentListener,
WindowFocusListener, and
Runnable. The listener interfaces will let the panel catch events indicating that the window has moved, been resized, or the focus changes. Implementing
Runnablewill let the panel create a thread to handle custom
repaint( )s.
The implementation of
ComponentListenerinvolves the four methods beginning with
component. They each simply call
repaint( ) so that the background will be updated whenever the user moves or resizes the window. Next are the two window focus handlers, which just call
refresh( ), as shown here:
public void refresh( ) {
if (frame.isVisible( )) {
repaint( );
refreshRequested = true ;
lastupdate = new Date( ).getTime( );
}
}
public void run( ) {
try {
while ( true ) {
Thread.sleep( 250 );
long now = new Date( ).getTime( );
if (refreshRequested &&
((now - lastupdate) > 1000 )) {
if (frame.isVisible( )) {
Point location = frame.getLocation( );
frame.hide( );
updateBackground( );
frame.show( );
frame.setLocation(location);
refresh( );
}
lastupdate = now;
refreshRequested = false ;
}
}
} catch (Exception ex) {
p(ex.toString( ));
ex.printStackTrace( );
}
}
refresh( ) ensures that the frame is visible and schedules a repaint. It also sets the
refreshRequestedboolean to true and saves the current time, which will become very important shortly.
The
run( ) method sleeps constantly, waking up every quarter-second to see if a refresh has been requested, and whether it has been more than a second since the last refresh. If more than a second has passed and the frame is actually visible, then
run( ) will save the frame location, hide it, update the background, then put the frame back in place and call
refresh( ). This ensures that the background is never updated more than needed.
So, why all of this rigmarole about using a thread to control refreshing? One word: recursion. The event handlers could simply call
updateBackground( ) and
repaint( ) directly, but hiding and showing the window to generate the screenshot would cause more focus-changed events. These would then trigger another background update, causing the window to hide again, and so on, creating an infinite loop. The new focus events are generated a few milliseconds after
refresh( ) is processed, so simply checking for an
isRecursingflag wouldn't stop a loop.
Additionally, any user action that would change the screen will probably create lots of events, not just one. It's just the last event that should trigger
updateBackground( ), not the first. To handle all these issues, the code creates a thread that watches for repaint requests and only processes a new screenshot if it hasn't already been done in the last 1,000 milliseconds. If the user generates events continuously for five seconds (searching for that lost browser window, for example), then only when everything else has settled down for a second will the refresh actually happen. This ensures that users won't have a window disappear out from under them while they are moving things around.
Another annoyance is that the window still has its border, which sort of ruins the effect of having a transparent background. Unfortunately, removing the borders with
setUndecorated(true)would also remove the titlebar and window controls. This probably isn't too much of a problem, though, because the types of applications that typically use shaped windows usually have draggable backgrounds [Hack #34].
Here's a simple test program to put this into action:
public static void main(String[] args) {
JFrame frame = new JFrame( " Transparent Window " );
frame.setUndecorated( true );
TransparentBackground bg = new TransparentBackground(frame);
bg.snapBackground( );
bg.setLayout( new BorderLayout( ));
JPanel panel = new JPanel( ) {
public void paintComponent(Graphics g) {
g.setColor(Color.blue);
Image img = new ImageIcon( " mp3.png " ).getImage( );
g.drawImage(img, 0 , 0 , null );
}
};
panel.setOpaque( false );
bg.add( " Center " ,panel);
frame.getContentPane( ).add( " Center " ,bg);
frame.pack( );
frame.setSize( 200 , 200 );
frame.setLocation( 500 , 500 );
frame.show( );
}
The code creates a faux MP3 player interface using a
JPanelsubclass and a PNG image with transparency. Note the call to
frame.setUndecorated(true), which turns off the border and titlebar. The call to
panel.setOpaque(false)turns off the default background (usually plain gray), allowing the screenshot background to shine through the transparent parts of the image (Figure 6-2). This produces a window that looks like Figure 6-3—a vision of Java programs to come?
Figure 6-2. Template for an MP3 player
Figure 6-3. Running the MP3 player
相关文章推荐
- 关于Swing实现透明窗体
- 关于Java Swing中的透明窗体
- 关于截屏实现透明窗体的bug
- SWING实现窗体透明效果
- Swing 透明窗体实现
- Swing 透明窗体实现
- VC透明窗体的实现
- Java Swing实现窗体添加背景图片的2种方法详解
- Win2000下编程实现窗体透明特效
- 本实例创建Swing窗体,单击窗体中的“写入文件”按钮实现写入功能,单击“读取文件”按钮实现从文件中读取信息显示在文本框中
- win32下gdiplus的使用/实现PNG图片透明窗体
- 一种基于 桌面截图的 透明窗体的实现1(理论)
- C#透明窗体实现方法
- java实现透明窗体
- 纯win32实现PNG图片透明窗体
- swing 窗体透明和窗体透明窗体里的空间不透明
- 纯win32实现PNG图片透明窗体
- 透明窗体的实现
- 关于Opengl中将24位BMP图片加入�一个alpha通道并实现透明的问题
- 另类的实现透明窗体