熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Swing 破局:打造半透明窗口

2013-11-23 19:22:21  來源: Java核心技術 

  要生成一個半透明的成形窗口而又要避免使用本地的編碼唯有靈活地應用screenshot(屏幕快照)

  半透明窗口是大眾對Swing最為渴求的特性之一 也可以稱之為定形窗口這種窗口有一部分是透明的可以透過它看到桌面背景和其它的程序如果不通過JNI(Java Native Interface 本地接口)Java是無法為我們生成一個半透明的窗口的(即使我們可以那樣做還得本地操作平台好支持半透明窗口才行)然而這些現狀無法阻止我們對半透明窗口的渴求通過一個我最喜歡的手段screenshot我們可以欺騙性地實現這個目的

  仿造這樣一個的半透明窗口的過程主要的通過以下幾點:
在窗口顯示之前先獲得一個screenshot;
把上一步獲取的屏幕快照作為窗口的背景圖
調整位置以便於我們捕獲的screenshot和實際當前的屏幕完美結合制造出一種半透明的假象

  剛剛說到的部分只是小兒科重頭戲在於如何在移動或變化半透明窗口時及時地更新screenshot也就是及時更新半透明窗口的背景

  在開始我們的旅行之前先生成一個類讓它繼承 JPanel我們用這個繼承類來捕獲屏幕並把捕獲的照片作為背景 類的具體代碼如下例

半透明背景組件
public class TransparentBackground extends Jcomponent {
    private JFrame frame;
    private Image background;

public TransparentBackground(JFrame frame) {
    thisframe = frame;
    updateBackground( );
}
/**
  * @todo 獲取屏幕快照後立即更新窗口背景
  */
public void updateBackground( ) {
    try {
        Robot rbt = new Robot( );
        Toolkit tk = ToolkitgetDefaultToolkit( );
        Dimension dim = tkgetScreenSize( );
        background = rbtcreateScreenCapture(
        new Rectangle((int)dimgetWidth( )
                          (int)dimgetHeight( )));
    } catch (Exception ex) {
        //p(extoString( ));
// 此方法沒有申明過因為無法得知上下文因為不影響執行效果先注釋掉它
        exprintStackTrace( );
    }
}
public void paintComponent(Graphics g) {
    Point pos = thisgetLocationOnScreen( );
    Point offset = new Point(posxposy);
    gdrawImage(backgroundoffsetxoffsetynull);
}
}
  首先構造方法把一個reference保存到父的JFrame然後調用updateBackground()方法在這個方法中我們可以利用javaawtRobot類捕獲到整個屏幕並把捕獲到的圖像保存到一個定義了的放置背景的變量中 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);
    bgsetLayout(new BorderLayout( ));
    JButton button = new JButton(This is a button);
    bgadd(Northbutton);
        JLabel label = new JLabel(This is a label);
    bgadd(Southlabel);
    framegetContentPane( )add(Centerbg);
    framepack( );
    framesetSize();
    frameshow( );
}
通過這段代碼運行出的效果如下圖所示

展示中的半透明窗口

  這段代碼相當簡單卻帶有兩個不足之處首先如果移動窗口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 = ;
    public boolean refreshRequested = true;
    public TransparentBackground(JFrame frame) {
        thisframe = frame;
        updateBackground( );
        frameaddComponentListener(this);
        frameaddWindowFocusListener(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(frameisVisible( )) {
        repaint( );
        refreshRequested = true;
        lastupdate = new Date( )getTime( );
    }
}
public void run( ) {
    try {
        while(true) {
            Threadsleep();
            long now = new Date( )getTime( );
            if(refreshRequested &&
                ((now lastupdate) > )) {
                if(frameisVisible( )) {
                    Point location = framegetLocation( );
                    framehide( );
                    updateBackground( );
                    frameshow( );
                framesetLocation(location);
                    refresh( );
                }
                lastupdate = now;
                refreshRequested = false;
                }
            }
        } catch (Exception ex) {
            p(extoString( ));
            exprintStackTrace( );
        }
    }

  refresh()可以保證frame可見並適時得調用repaint()它也會對refreshRequest變量置真(true)同時保存當前時間值現在所做的這些對接下來要做的事是非常重要的鋪墊

  除了每四分之一秒被喚醒一次用來檢測是否有新的刷新的要求或者是否離上次刷新時間超過了一秒方法run()一般地處於休眠狀態如果離上次刷新超過了一秒並且frame是可見的那麼run()將保存frame的位置隱藏frame獲取一個screenshot更新frame背景再根據隱藏frame時保存的位置信息重新顯示已經更新了背景的frame接著調用refresh()方法通過這樣的控制使得背景更新不至於比需要的多太多

  那麼我們為什麼要對用一個線程控制刷新如此長篇大論呢?一個詞遞歸事件處理可以直接輕松地調用repaint()但是隱藏和顯示窗口已便於獲取screenshot 卻交替了很多得焦失焦事件所有這些都會觸發一個新的背景更新導致窗口再次被隱藏如此往返將導致永無止境的循環一個新的得焦事件將在執行refresh()幾毫秒之後被調用所以簡單地檢測isRecursing標志是無法阻止循環的繼續

  另外用戶任意一個改變屏幕的動作將會隨之引出一堆的事件來而不僅僅是簡單一個應該是最後一個事件去觸發updateBackground()而不是第一個為了全面解決這些問題代碼產生一個線程然後用這個線程去監控重畫(repaint)要求並保證當前的執行動作是發生在過去的毫秒內沒有發生過此動作如果一個客戶每五秒不間斷地產生事件(比如尋找丟失的浏覽窗口)那麼只有在其它所有工作在一秒內完成才執行更新這樣就避免了用戶不至於在移動東西時窗口卻消失不見了的尴尬

  另一件煩惱的事就是我們的窗口仍舊有邊框這條邊框使得我們無法完美和背景融為一體更為痛苦的是使用setUndecorated(true)移除邊框時我們的標題欄和窗口控制欄也跟著移除了可是這也算不上是什麼大問題因為那類使用定形窗口的應用程序一般都具有可拖動的背景【Hack#

  接下來我們在下面這個簡單的測試程序中把所講的東西落實進去
public static void main(String[] args) {
    JFrame frame = new JFrame(Transparent Window);
    framesetUndecorated(true);
    
    TransparentBackground bg = new TransparentBackground(frame);
    bgsnapBackground( );
    bgsetLayout(new BorderLayout( ));

   JPanel panel = new JPanel( ) {
        public void paintComponent(Graphics g) {
            gsetColor(Colorblue);
            Image img = new ImageIcon(mppng)getImage( );
            gdrawImage(imgnull);
        }
    };
    panelsetOpaque(false);

    bgadd(Centerpanel);

    framegetContentPane( )add(Centerbg);
    framepack( );
    framesetSize();
    framesetLocation();
    frameshow( );
}
  這段代碼通過繼承JPanel加上一個透明的PNG格式圖片人工生成一個mp播放器界面注意使用了setUndecorated()來隱藏邊框和標題欄調用setOpaque(false)將隱藏默認的背景(一般為灰色)這樣screenshot的背景就可以和圖片中透明的部分合成一個整體去配合程序窗口周圍的屏幕背景(如圖)通過一系列的努力就可以看到圖的效果是不是很讓人驚詫?會不會感歎Java的新版本騰空出世?


mp 播放器外觀模板

image  
運行中的mp播放器


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26738.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.