Swing破局:打造半透明窗口

来源:互联网 发布:excel剔除重复数据公式 编辑:程序博客网 时间:2024/05/01 18:55
leniz 发表于 2006-01-23
点击数:1254 评论数:12 评价:15/3
关键词:Swing

摘要:

编者注:下面这个来自《Swing Hacks》一书的例子是原书中一个可以看到的最为大胆的破局-模仿一个不规则外形的窗口。你可能在mp3播放器的默认皮肤中找的到它的身影.

文章工具

收藏
投票评分
发表评论
复制链接
编者注:下面这个来自《Swing Hacks》一书的例子是原书中一个可以看到的最为大胆的破局-模仿一个不规则外形的窗口。你可能在mp3播放器的默认皮肤中找的到它的身影.这里之所以要称之为“破局”是迫于Java不支持不规则窗口的事实所致,所以不得不作出的选择就是让Java的窗口知道它所覆盖的下面有什么,以及处理窗口矩形内的图片区域而不是窗口本身的不规则形状.至于如何实现,那么请继续我们的阅读.

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Joshua;Chris;leniz(作者的Blog:http://blog.matrix.org.cn/page/leniz)
原文:http://www.onjava.com/pub/a/onjava/excerpt/swinghks_hack41/index.html
译文:http://www.matrix.org.cn/resource/article/44/44186_Swing.html
关键字:Swing
  
要生成一个半透明的成形窗口,而又要避免使用本地的编码,唯有灵活地应用screenshot(屏幕快照).

半透明窗口是大众对Swing最为渴求的特性之一. 也可以称之为定形窗口,这种窗口有一部分是透明的,可以透过它看到桌面背景和其它的程序.如果不通过JNI(Java Native Interface 本地接口)Java是无法为我们生成一个半透明的窗口的(即使我们可以那样做,还得本地操作平台好支持半透明窗口才行).然而这些现状无法阻止我们对半透明窗口的渴求,通过一个我最喜欢的手段screenshot,我们可以欺骗性地实现这个目的.

仿造这样一个的半透明窗口的过程,主要的通过以下几点:
1.        在窗口显示之前,先获得一个screenshot;
2.        把上一步获取的屏幕快照,作为窗口的背景图
3.        调整位置,以便于我们捕获的screenshot和实际当前的屏幕完美结合,制造出一种半透明的假象.

刚刚说到的部分只是小儿科,重头戏在于,如何在移动或变化半透明窗口时,及时地更新screenshot,也就是及时更新半透明窗口的背景.

在开始我们的旅行之前,先生成一个类,让它继承 JPanel,我们用这个继承类来捕获屏幕,并把捕获的照片作为背景. 类的具体代码如下例6-1

例 6-1 。 半透明背景组件
public class TransparentBackground extends Jcomponent { 
    private JFrame frame;
    private Image background;

public TransparentBackground(JFrame frame) {
    this.frame = frame;
    updateBackground( );
}
/**
  * @todo 获取屏幕快照后立即更新窗口背景
  */
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);
}
}


首先,构造方法把一个reference保存到父的JFrame,然后调用updateBackground()方法,在这个方法中,我们可以利用java.awt.Robot类捕获到整个屏幕,并把捕获到的图像保存到一个定义了的放置背景的变量中. paintComponent()方法可以帮助我们获得窗口在屏幕上的绝对位置,并用刚刚得到的背景作为panel的背景图,同时这个背景图会因为panel位置的不同而作对应的移动,以使panel的背景和panel覆盖的那部分屏幕图像无缝重叠在一起,同时也就使panel和周围的屏幕关联起来.

我们可以通过下面这个main方法简单的运行一下,随便放置一些组件到panel上,再把panel放置到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( );
}


通过这段代码,运行出的效果如下图6-1所示:
image
图6-1 展示中的半透明窗口

这段代码相当简单,却带有两个不足之处。首先,如果移动窗口,panel中的背景无法自动的更新,而paintComponent()只在改变窗口大小时被调用;其次,如果屏幕曾经发生过变化,那么我们制作的窗口将永远无法和和屏幕背景联合成整体。

谁也不想时不时地跑去更新screenshot,想想看,要找到隐藏于窗口后的东西,要获得一份新的screenshot,还要时不时的用这些screenshot来更新我们的半透明窗口,这些事情足以让用户无法安心工作。事实上,想要获取窗口之外的屏幕的变化几乎是不太可能的事,但多数变动都是发生在foreground窗口发生焦点变化或被移动之时。如果你接受这的观点(至少我接受这个观点),那么你可以只监控下面提到的几个事件,并只需在这几个事件被触发时,去更新screenshot。
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( ); }


首先,让我们的半透明窗口即panel实现ComponentListener接口,
WindowFocusListener接口和Runnable接口。Listener接口可以帮助我们捕获到窗口的移动,大小变化,和焦点变化。实现Runnable接口可以使得panel生成一个线程去控制定制的repaint()方法。

ComponentListener接口带有四个component开头的方法。它们都可以很方便地调用repaint()方法,所以窗口的背景也就可以随着窗口的移动,大小的变化而相应地更新。还有两个是焦点处理的,它们只调用refresh(),如下示意:
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()可以保证frame可见,并适时得调用repaint()。它也会对refreshRequest变量置真(true),同时保存当前时间值,现在所做的这些对接下来要做的事是非常重要的铺垫。

除了每四分之一秒被唤醒一次,用来检测是否有新的刷新的要求或者是否离上次刷新时间超过了一秒,方法run()一般地处于休眠状态。如果离上次刷新超过了一秒并且frame是可见的,那么run()将保存frame的位置,隐藏frame,获取一个screenshot,更新frame背景,再根据隐藏frame时保存的位置信息,重新显示已经更新了背景的frame,接着调用refresh()方法。通过这样的控制,使得背景更新不至于比需要的多太多。

那么我们为什么要对用一个线程控制刷新如此长篇大论呢?一个词:递归。事件处理可以直接轻松地调用repaint(),但是隐藏和显示窗口已便于获取screenshot 却交替了很多“得焦”和“失焦”事件。所有这些都会触发一个新的背景更新,导致窗口再次被隐藏,如此往返,将导致永无止境的循环。一个新的“得焦”事件,将在执行refresh()几毫秒之后被调用,所以简单地检测isRecursing标志是无法阻止循环的继续。

另外,用户任意一个改变屏幕的动作,将会随之引出一堆的事件来,而不仅仅是简单一个。应该是最后一个事件去触发updateBackground(),而不是第一个。为了全面解决这些问题,代码产生一个线程,然后用这个线程去监控重画(repaint)要求,并保证当前的执行动作是发生在过去的1000毫秒内没有发生过此动作。如果一个客户每五秒不间断地产生事件(比如,寻找丢失的浏览窗口),那么只有在其它所有工作在一秒内完成才执行更新。这样就避免了,用户不至于在移动东西时,窗口却消失不见了的尴尬。

另一件烦恼的事就是,我们的窗口仍旧有边框,这条边框使得我们无法完美和背景融为一体。更为痛苦的是使用setUndecorated(true)移除边框时,我们的标题栏和窗口控制栏也跟着移除了。可是这也算不上是什么大问题,因为那类使用定形窗口的应用程序一般都具有可拖动的背景【Hack#34】

接下来,我们在下面这个简单的测试程序中把所讲的东西落实进去:
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( );
}


这段代码通过继承JPanel,加上一个透明的PNG格式图片,人工生成一个mp3播放器界面。注意使用了setUndecorated()来隐藏边框和标题栏。调用setOpaque(false),将隐藏默认的背景(一般为灰色),这样screenshot的背景就可以和图片中透明的部分合成一个整体,去配合程序窗口周围的屏幕背景。(如图6-2)通过一系列的努力,就可以看到图6-3的效果。是不是很让人惊诧?会不会感叹Java的新版本腾空出世?

image
图 6-2. mp3 播放器外观模板

image  
图 6-3. 运行中的mp3播放器

Joshua Marinacci ,Java.net的专栏作家,所撰写的专栏“The Java Sketchbook”,此专栏覆盖的问题是关于Java客户端和web开发的。
Chris Adamson ONJava的编辑,同时也是java.net的编辑,来自亚特兰大的顾问,专长于Java,Mac OS X,和 媒体领域。

本页面地址:

→投票评分

    

→用户评论列表

 
6.无bg.snapBackground( )方法,


以下是 已改正的程序, 可以实现无边框移动 .

有两个类 .一个是原来的 另一个是为了处理无边框移动

package com.leniz;
import java.awt.*;

import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.event.*;
import java.awt.event.*;
import java.util.*;

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

    /**
     * @todo  获取背景图片
     *
     */
    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( );
      }
    }
    /**
     * @todo 通过当前frame的大小和位置在 background背景中截取图
     *       作为当前frame的背景图片
     */
    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);
    }
    

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

    /**
     * @todo  用上面获取的图片更新背景
     *
     */
    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.setVisible(false );
                      updateBackground( );
                      frame.setVisible(true );
                  frame.setLocation(location);
                      refresh( );
                  }
                  lastupdate = now;
                  refreshRequested = false;
                  }
              }
          } catch (Exception ex) {
             ex.printStackTrace( );
          }
      }
  
    public static void main(String[] args) {
      
      JFrame frame = new JFrame("Transparent Window");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);      
      frame.setUndecorated(true);
      TransparentBackground bg = new TransparentBackground(frame);
      bg.setLayout(new BorderLayout( ));      
      JPanel panel = new JPanel( ) {
        public void paintComponent(Graphics g) {
            g.setColor(Color.blue);
            Image img = new ImageIcon("mp3.gif").getImage( );
            g.drawImage(img,0,0,null);
        }
      };      
      panel.setOpaque(false);
      bg.add("Center",panel);
      
      //关闭按键
      JButton exit = new JButton("Exit");
      exit.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e)
        {
          System.exit(0);
        }
      });
      frame.getContentPane().add(exit, BorderLayout.SOUTH);
      frame.getContentPane( ).add("Center",bg);
      frame.pack( );
      frame.setSize(130,160);
      
      new EasyMove().install(frame); //无边框移动 处理  
      
      frame.setLocation(400,400);
      frame.setVisible(true);
    }
    

}


package com.leniz;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.border.BevelBorder;

public class EasyMove extends MouseAdapter implements MouseMotionListener {

  private Point offset;
  private Component host;



  public synchronized void install(Component comp)
  {
    uninstall();
    host = comp;
    host.addMouseListener(this);
    host.addMouseMotionListener(this);
  }

  public synchronized void uninstall()
  {
    if (host != null) {
      host.removeMouseListener(this);
      host.removeMouseMotionListener(this);
      host = null;
    }
  }

  public void mousePressed(MouseEvent e)
  {
    if (e.getSource() == host) offset = e.getPoint();
  }

  public void mouseDragged(MouseEvent e)
  {
    if (e.getSource() != host) return;

    final int x = host.getX();
    final int y = host.getY();
    final Point lastAt = e.getPoint();
    host.setLocation(x + lastAt.x - offset.x, y + lastAt.y - offset.y);
  }

  public void mouseMoved(MouseEvent e)
  {
  }
}


透明图片



#8098 评论作者:bobrow 发表时间:2006-02-07 02:55
请问bg.snapBackground( );哪里来的?
#8052 评论作者: leniz 发表时间:2006-02-07 10:36
谢谢大家指出这些错误和以及其它不同的方法.

开始翻译时我都用的方法是: 一个英语文档,一个中文文档, 翻译过程,我都是逐一的测试过程序的,有些是存在问题的,可能这些问题是转贴过程中的遗漏,或排版过程中的失误,我的原则是尽我所能的改掉一些明显的错误,比如冒出来的方法,引用类库的大小写等等 非逻辑性错误.

  要提交的时候 我就把中文的翻译逐一贴到英文文档段落后, 因为要运行的程序是没有翻译的(忘了可能有改动,虽然这种改动只是为了能运行出结果),所以程序原封不动的直接用了原来的.  这样一些错误就没有被指出来.
      很抱歉自己的大意.以后的翻译我一定会更为细心一点.
     从原网站转来的程序存在下面的几个问题.
    1.Jcomponent  应该为  JComponent  第二个字母大写
    2.错误打印中的  p(ex.toString( )) 是一个没有来历的方法,注释掉它.
    3..show()方法是一个淘汰的方法,用 setVisible(true) 来替代 同样
     隐藏的hide() 改为setVisible(false);{不过关系不大}
    4. 下载的图片是不透明,自己搞一下,转化成透明图片,而且要放对位置哦.
   5. 去掉框架后,无法移动.[不知道其它人有没有这个问题 ,我正在找方法]
#8032 评论作者:zs_3718 发表时间:2006-02-06 10:22
http://examples.oreilly.com/swinghks/swing-hacks-examples-20060109.zip
里面的Ch06-TransparentAndAnimatedWindows/41的例子
import javax.swing.*;
import java.awt.*;
public class BGTest2 {
    
    public static void main(String[] args) {
        JFrame frame = new JFrame("Transparent Window");
        frame.setUndecorated(true);
        TransparentBackground bg = new TransparentBackground(frame);
        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();
    }

}
没有bg.snapBackground( );啊
#8020 评论作者: galen_wang 发表时间:2006-02-05 04:33
楼上的程序,透明的比较模糊,初看程序不知道是什么原因?
#7969 评论作者:xqmusic 发表时间:2006-01-31 03:12
转帖一个同样实现了透明窗体的代码

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

public class TransparentBackground extends JComponent
                implements ComponentListener, WindowFocusListener, Runnable {

        // constants ---------------------------------------------------------------

        // instance ----------------------------------------------------------------

        private JFrame _frame;
        private BufferedImage _background;
        private long _lastUpdate = 0;
        private boolean _refreshRequested = true;

        private Robot _robot;
        private Rectangle _screenRect;

        private ConvolveOp _blurOp;

        // constructor -------------------------------------------------------------

        public TransparentBackground(JFrame frame) {
                _frame = frame;

                try {
                        _robot = new Robot();
                } catch (AWTException e) {
                        e.printStackTrace();
                        return;
                }

                Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
                _screenRect = new Rectangle(dim.width, dim.height);

                float[] my_kernel = {
                                0.10f, 0.10f, 0.10f,
                                0.10f, 0.20f, 0.10f,
                                0.10f, 0.10f, 0.10f};
                _blurOp = new ConvolveOp(new Kernel(3, 3, my_kernel));

                updateBackground();
                _frame.addComponentListener(this);
                _frame.addWindowFocusListener(this);
                new Thread(this).start();
        }

        // protected ---------------------------------------------------------------

        protected void updateBackground() {
                _background = _robot.createScreenCapture(_screenRect);
        }

        protected void refresh() {
                if (_frame.isVisible() && this.isVisible()) {
                        repaint();
                        _refreshRequested = true;
                        _lastUpdate = System.currentTimeMillis();
                }
        }

        // JComponent --------------------------------------------------------------

        protected void paintComponent(Graphics g) {
                Graphics2D g2 = (Graphics2D) g;

                Point pos = this.getLocationOnScreen();

                BufferedImage buf = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
                buf.getGraphics().drawImage(_background, -pos.x, -pos.y, null);

                Image img = _blurOp.filter(buf, null);
                g2.drawImage(img, 0, 0, null);

                g2.setColor(new Color(255, 255, 255, 0));
                g2.fillRect(0, 0, getWidth(), getHeight());
        }

        // ComponentListener -------------------------------------------------------

        public void componentHidden(ComponentEvent e) {
        }

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

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

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

        // WindowFocusListener -----------------------------------------------------

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

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

        // Runnable ----------------------------------------------------------------

        public void run() {
                try {
                        while (true) {
                                Thread.sleep(100);
                                long now = System.currentTimeMillis();
                                if (_refreshRequested && ((now - _lastUpdate) > 1000)) {
                                        if (_frame.isVisible()) {
                                                Point location = _frame.getLocation();
                                                _frame.setLocation(-_frame.getWidth(), -_frame.getHeight());
                                                updateBackground();
                                                _frame.setLocation(location);
                                                refresh();
                                        }
                                        _lastUpdate = now;
                                        _refreshRequested = false;
                                }
                        }
                } catch (InterruptedException e) {
                        e.printStackTrace();
                }
        }

        public static void main(String[] args) {
                JFrame frame = new JFrame("Transparent Window");
                TransparentBackground bg = new TransparentBackground(frame);
                bg.setLayout(new BorderLayout());
                frame.getContentPane( ).add("Center",bg);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(200, 200);
                frame.setLocation(500, 500);
                frame.setVisible(true);
        }
}
#7966 评论作者:xqmusic 发表时间:2006-01-31 02:35
刚看过,感觉不是很理想,第一用线程来刷新这个方法属于苯方法
第二,bg.snapBackground( );是哪来的?JComponent类本身可没有这个方法,自己写的?
#7916 评论作者: galen_wang 发表时间:2006-01-24 02:46
bg.snapBackground( );
这个方法没有,
#7912 评论作者: hunter_z 发表时间:2006-01-24 11:47
看上去不错的样子,我来试着体验一下先
视觉效果也很重要啊.
#7907 评论作者: cyicecream 发表时间:2006-01-24 09:40
效果不错,不晓得速度上会不会比较慢。
一直感觉SWING做的界面刷新比较慢,jdk1.4下
#7892 评论作者: foxs 发表时间:2006-01-23 10:06
我以前也搞过半透明的窗体
不过没这么有新意
#7891 评论作者: li_nummereins 发表时间:2006-01-23 09:06
要是出现封装好的工具包,一定会引起巨大反响。现在用户体验越来越重要了。
#8115 评论作者: leniz 发表时间:2006-02-08 02:12 非常好 还行 一般 扔鸡蛋          总得分:15 / 投票人次:3
原创粉丝点击