dyerac  
dyerac In Java
公告

日历
<2006年12月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456
统计
  • 随笔 - 36
  • 文章 - 10
  • 评论 - 94
  • 引用 - 0

导航

常用链接

留言簿(5)

随笔分类(49)

随笔档案(36)

文章分类(11)

文章档案(10)

相册

dyerac

搜索

  •  

积分与排名

  • 积分 - 78759
  • 排名 - 706

最新随笔

最新评论

阅读排行榜

评论排行榜

 

先把以前写的转过来,呵呵



图形界面开发对于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

posted on 2006-04-03 18:15 dyerac in java... 阅读(8948) 评论(13)  编辑  收藏 所属分类: 原创文章JavaSE
评论:
  • # re: 用java打造任意形状窗口和透明窗口  nake Posted @ 2006-04-03 20:32
    不错
      回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  fanse Posted @ 2006-04-05 00:19
    好文章,我要充电了

    欢迎访问 http://www.shuangzixing.net 双子星Java开源技术门户  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  jazzy Posted @ 2006-04-06 16:21
    呵呵,用SWT实现就很简单啦  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  -_=~~ Posted @ 2006-08-07 11:54
    不错不错,多谢

    swt....作者不是要跨平台么..hoho  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  Tony[匿名] Posted @ 2006-12-04 23:19
    很感謝您發表自己的感想

    其實這是沒用的小技巧
    試想:使用者不可能不去移動這個視窗,一移動,就露馬腳了

    很久以前我用過這個技巧
    卻在內部RD就被否決掉

    原因就在:無法預測試用者的操作方式  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  dyerac in java... Posted @ 2006-12-05 12:38
    @Tony[匿名]
    不啊,移动起来是没有问题的
    只是在隐藏后再显示时会出现一个瞬移的现象,但是基本上用户是注意不到的  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  pc Posted @ 2007-09-20 11:47
    看到你的src里有一行
    //UIManager.setLookAndFeel(new QuaquaLookAndFeel());
    这个是windows下的L&F吗?
    能不能给我发一份?hongmocai at 126.com
    Thank you.  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  ki_bum Posted @ 2008-02-24 17:56
    偶..最近..也在看Hack..
    但..L&F..MS讲的有点少...
    呵~~~
    只有一个...小问题...
    如果...是双屏显示器(3屏..4屏~~~)
    在非主屏上...
    程序..就会认为是windowLostFocus
    就会显示L&F的background(==!)  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  evilz Posted @ 2009-02-13 14:28
    哈哈,这个东西我曾经也实现过。楼上某位说不能移动,这个很好解决,自写窗体事件,它只要一动,马上重新截屏重新绘制窗体。  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  hello Posted @ 2009-05-13 21:10
    题目说的太大了。。你只是做了“透明”效果。。但可没有做到使窗体呈任意形状。。。我就冲这个任意形状来的。。。结果。。。。。。  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  dyerac Posted @ 2009-05-14 14:36
    @hello
    仔细看完再说咯:
    ------
    ps:你也许还会说,e?怎么没讲任意形状窗口如何打造?
    呵呵,其实只要把窗口按上述方法设置成透明,再去掉边框,再换上自己的边框,不就完成了马?
    相信聪明的各位一定很容易就办到咯  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  第一次 Posted @ 2009-06-21 01:21
    我很少留言的,这次如果不说声谢谢我都过意不去了!  回复  更多评论   

  • # re: 用java打造任意形状窗口和透明窗口  月下幽杳 Posted @ 2013-06-05 12:35
    根本没必要搞这么复杂,
    this.setUndecorated(true);
    AWTUtilities.setWindowOpaque(this, false);
    然后调用一个JLabel来显示图片(png部分透明图片),然后作为底层容器,可以实现任意窗口  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
博客园   IT新闻   Chat2DB   C++博客   博问  
 
 
Copyright © dyerac in java... Powered by: 博客园 模板提供:沪江博客