« | August 2025 | » | 日 | 一 | 二 | 三 | 四 | 五 | 六 | | | | | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | | | | | | | |
| 公告 |
本博客在此声明所有文章均为转摘,只做资料收集使用。并无其他商业用途。 |
Blog信息 |
blog名称: 日志总数:210 评论数量:205 留言数量:-19 访问次数:918797 建立时间:2007年5月10日 |

| |
[界面和模板语言]用java打造任意形状窗口和透明窗口 文章收藏, 网上资源, 软件技术, 电脑与网络
李小白 发表于 2007/7/19 10:05:13 |
lhwork 发表于 2006-10-23 9:58:31
先把以前写的转过来,呵呵 500)this.width=500'> 图形界面开发对于Java来说并非它的长项,开发者经常会碰到各种各样的限制,比如,如何打造一款任意形状的窗口?如何可以透过窗口显示它覆盖下的内容?
考虑到Java并没有被设计成支持以上的功能,所以,你能得到的永远是方方正正的窗口,毫无新意,当然,我们可以通过JNI调用本地代码来完成,但是这就失去了java可移植性的意义,那么,用纯粹的java代码如何实现以上两种功能呢?
下文提供了一个实现的参考
预备知识:1.java.awt.Robot,这个类是一个功能非常强大的类,通过它我们可以控制鼠标和键盘的响应,不过这里,我们只需要用到它的一个功能--截屏,也就是得到当前屏幕的快照(screenshot)2.我们可以通过调用一个swing组件的paintComponent(Graphics g)方法用特定的Graphics为其定制特殊的外观
首先声明的一点是,本文中的实现方法只是一个欺骗的手法,因为要想实现前文中的功能,我们几乎的重写一套Swing出来,这里,简便的做法是,我们通过 robot类来获得当前屏幕的快照,然后贴在我们需要的窗口上,这样,不就实现了透明的功能了吗?顺便提一下,几年前日本发明的隐形衣也是依靠这种机制,这种衣服在身后附带一套摄像机,然后即时的将拍下的内容显示在衣服的正面,因此,当别人看过来时,仿佛就通过人和衣服看到了身后的场景^_^
另外,还要感谢Joshua Chris leniz的Swing Hack一书,以上充满创新的方法正来自他的书中
好咯,让我们具体看一下细节的处理:第一,我们要得到当前屏幕的快照,保存到一个Image对象[color=Red]background[/color]中: public void updateBackground() { try { Robot rbt = new Robot(); Toolkit tk = Toolkit.getDefaultToolkit(); Dimension dim = tk.getScreenSize(); [color=Red]background [/color]= rbt.createScreenCapture(new Rectangle(0, 0, (int) dim .getWidth(), (int) dim.getHeight())); } catch (Exception ex) { } }第二,我们需要让窗口显示这个图像,也就是说,让窗口的背景图像是这副图像,以达到透明的欺骗效果:
public void paintComponent(Graphics g) { Point pos = this.getLocationOnScreen(); Point offset = new Point(-pos.x, -pos.y); g.drawImage([color=Red]background[/color], offset.x, offset.y, null); }
在swing hack一书中,作者也给出了他的实现,然而,运行结果表明该实现存在很大的问题:窗口经常无法即时更新,往往背景变了,窗口里显示的却还是以前的背景。仔细研究了他的代码,我发现了问题的根结,同时也是这种实现技术的关键要素--如何让窗口在正确的时机里更新显示,下面,我们会讨论这一点
第三,更新窗口外观前两步的操作,只能得到一副当前屏幕的快照,一旦背景变化了,或者窗口移动了,变化大小了,那么我们制作的窗口将永远无法和和屏幕背景联合成整体,也就失去了透明的效果;同时,我们也不可能每时每刻都调用updateBackground() 方法获得最新的背景,可行的方法是,通过对事件的监听来选择适当的时间来更新外观
我们应该可以达到这三点共识:1。窗口移动或改变大小时,背景图像一般是不会发生变化的,我们不需要得到新的屏幕快照,只用将以前得到的背景中适当的部分贴到窗口上,调用repaint()方法就足已2。要获得最新的屏幕快照,必须先将窗口隐藏起来,调用updateBackground() 得到图像后再把窗口显示,我们可以用refresh方法来表示 refresh(){ frame.hide(); updateBackground() ; frame.show();}3。如果背景改变了,那么一定是别的windows程序获得了或失去了事件焦点,也就是说我们关注的窗口将触发焦点得失事件,这个时候需要调用refresh() 得到最新的屏幕快照
看到这里,你或许认为已经没有技术难点了,然而,此时才是我们[color=Red]最需要关注的地方[/color]:参看第三点,我们需要在窗口得失焦点时调用refresh() 方法;参看第一点,我们调用refresh() 方法时需要先将窗口隐藏,然后再显示。于是问题来了,在隐藏和显示窗口时,同样会触发得失焦点事件,得失焦点事件又将触发新的隐藏和显示窗口事件(为了得到新的屏幕快照),这就使程序陷入了死循环中,我们必须加以控制,使得第二次触发焦点得失事件时不调用refresh()方法
作者的办法是加一个线程来控制,通过判断时间间隔长短来决定是否调用refresh()方法,可是,这个条件是程序非常的不稳定,因为往往调用时间会根据系统繁忙度而改变,使得需要更新时不能更新,不需要更新的时候反而更新了因此,我决定采取新的解决方案,能不能隐藏/显示窗口时不触发得失焦点事件呢?解决方法很简单,我抛开了传统的setVisible()或者show(),hide()方法,而是使用setLocation()方法,因为调用 setLocation()方法时窗口不会失去焦点,同时,只要用类似setLocation(-2000,-2000)方法也同样可以轻松的让窗口在屏幕中消失下面是我的全部代码:
import java.awt.BorderLayout;import java.awt.Dimension;import java.awt.Graphics;import java.awt.Image;import java.awt.Point;import java.awt.Rectangle;import java.awt.Robot;import java.awt.Toolkit;import java.awt.event.ComponentEvent;import java.awt.event.ComponentListener;import java.awt.event.WindowEvent;import java.awt.event.WindowFocusListener;
import javax.swing.JButton;import javax.swing.JComponent;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.UIManager;
import org.jvnet.substance.SubstanceLookAndFeel;import org.jvnet.substance.theme.SubstanceLightAquaTheme;
import ch.randelshofer.quaqua.QuaquaLookAndFeel;
public class TestEvent extends JComponent implements ComponentListener, WindowFocusListener { private JFrame frame;
private Boolean isHiding = false, isShowing = false, start = false;
private Image background;
private Point p; [color=Red]//获得当前屏幕快照[/color] 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(); }
}
[color=Red] //将窗口掉离出屏幕以获得纯粹的背景图象[/color] public void refresh() { if (start == true) { this.updateBackground(); frame.setLocation(p); if (p.x < 0 || p.y < 0) frame.setLocation(0, 0); this.repaint(); } }
public void componentHidden(ComponentEvent e) { // TODO Auto-generated method stub System.out.println("Hidden"); }
[color=Red] //窗口移动时[/color] public void componentMoved(ComponentEvent e) { // TODO Auto-generated method stub System.out.println("moved"); this.repaint(); }
[color=Red] //窗口改变大小时[/color] public void componentResized(ComponentEvent e) { // TODO Auto-generated method stub System.out.println("resized"); this.repaint(); }
public void componentShown(ComponentEvent e) { // TODO Auto-generated method stub System.out.println("shown"); }
[color=Red] //窗口得到焦点后,用refresh()方法更新界面[/color] public void windowGainedFocus(WindowEvent e) { // TODO Auto-generated method stub System.out.println("gainedFocus"); refresh(); start = false; }
[color=Red] //窗口失去焦点后,将其移出屏幕[/color] public void windowLostFocus(WindowEvent e) { // TODO Auto-generated method stub System.out.println("lostFocus"); if (frame.isShowing() == true) { System.out.println("visible"); } else { System.out.println("invisible"); } start = true; p = frame.getLocation(); frame.setLocation(-2000, -2000); }
public TestEvent(JFrame frame) { super(); this.frame = frame; updateBackground(); this.setSize(200, 120); this.setVisible(true); frame.addComponentListener(this); frame.addWindowFocusListener(this);
// TODO Auto-generated constructor stub }
//绘制外观,注意,其中 pos,offset 是为了将特定部分的图象贴到窗口上 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); }
/** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { // UIManager.setLookAndFeel("org.fife.plaf.Office2003.Office2003LookAndFeel"); // UIManager.setLookAndFeel("org.fife.plaf.OfficeXP.OfficeXPLookAndFeel"); // UIManager.setLookAndFeel("org.fife.plaf.OfficeXP.OfficeXPLookAndFeel"); UIManager.setLookAndFeel(new SubstanceLookAndFeel()); //UIManager.setLookAndFeel(new SmoothLookAndFeel()); //UIManager.setLookAndFeel(new QuaquaLookAndFeel()); UIManager.put("swing.boldMetal", false); if (System.getProperty("substancelaf.useDecorations") == null) { JFrame.setDefaultLookAndFeelDecorated(true); //JDialog.setDefaultLookAndFeelDecorated(true); } System.setProperty("sun.awt.noerasebackground", "true"); SubstanceLookAndFeel.setCurrentTheme(new SubstanceLightAquaTheme());
// UIManager.setLookAndFeel("org.fife.plaf.VisualStudio2005.VisualStudio2005LookAndFeel"); } catch (Exception e) { System.err.println("Oops! Something went wrong!"); }
JFrame frame = new JFrame("Transparent Window"); TestEvent t = new TestEvent(frame); t.setLayout(new BorderLayout()); JButton button = new JButton("This is a button"); t.add("North", button); JLabel label = new JLabel("This is a label"); t.add("South", label); frame.getContentPane().add("Center", t); frame.pack(); frame.setSize(150, 100); frame.show(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // t.start=true; }
}
当然,以上代码还存在许多不足,比如在移动窗口时性能会有影响,应该可以通过线程来控制其更新的频率来提升效率,希望大家能一起研究一下,改好了记得通知我哦
ps:你也许还会说,e?怎么没讲任意形状窗口如何打造? 呵呵,其实只要把窗口按上述方法设置成透明,再去掉边框,再换上自己的边框,不就完成了马? 相信聪明的各位一定很容易就办到咯
另外,我还在下面附带了作者的大论,想了解更多的同学也可以去看看
http://www.matrix.org.cn/resource/article/44/44186_Swing.html |
|
|