引言
  本文將講述如何應用jfc/swing內建的圖像組件來創建完全自定義的基于圖像的用戶接口。
  大多數swing應用程序是通過標準vm提供的,或者是客戶提供的外觀和感覺(l&f)來獲取它們的外在展示。l&f是一個完整的體系架構,vm需要做很多內在的工作,并且它還不是完全自定義的。舉個例子來說吧,在基于l&f的前提下,我們可以創建一個按鈕,看起來有點像交通崗上的"紅燈",隨之而來的在你的應用中所有的按鈕就都有了這樣的"相貌"。然而有時我們所真正需要的就是一個看起來與圖像完全一樣的按鈕,就像web上面基于圖像的按鈕一樣。
  為了讓大家更好的了解我們所要介紹的內容,先來看一下最終的顯示效果,如下圖所示:一個帶面板(panel)的窗格(frame)包含了一個標簽(label),一個按鈕(button),一個復選框(checkbox)。面板、標簽和按鈕完全由圖像繪制,完全沒有使用到標準的l&f。復選框是一個標準的checkbox,但它將被設計成是透明的以搭配圖像背景。

  第一步 創建背景panel
  要完成這"天堂"般的工程,首先我們要做的是創建一個圖像背景。因為這樣的組件可重用性(reusable)很高,所以我們創建了jpanel類的一個子類,叫做imagepanel,參考下面的代碼示例: 
  示例代碼1:
package com.demo.jcomponents;
import java.awt.*;
import javax.swing.*;
/**
* 創建圖像面板
* @author xiazhi
*/
public class imagepanel extends jpanel
{
 /**
 * 圖形對象
 */
 private image img;
 /**
 * 構造函數
 * 
 * @param img 圖像對象
 */
 public imagepanel(string img)
 {
  this(new imageicon(img).getimage());
 }
 /**
 * 構造函數
 * 
 * @param img 圖像對象
 */
 public imagepanel(image img)
 {
  this.img = img;
  // 定義圖像尺寸
  dimension size = new dimension(img.getwidth(null), img.getheight(null));
  setpreferredsize(size);
  setminimumsize(size);
  setmaximumsize(size);
  setsize(size);
  // 定義布局方式為空
  setlayout(null);
 }
 /**
 * 重畫畫布
 */
 public void paintcomponent(graphics g)
 {
  g.drawimage(img, 0, 0, null);
 }
}
  構造函數使用image的實例作為參數,并將繪制的圖像保存在變量img中以備以后使用。接著調用setsize()和setpreferredsize()方法,并以圖像的尺寸作為參數。這樣可以確保panel的尺寸與圖像的尺寸完全一致。接下來的操作非常重要,必須顯示地指定panel的preferred、maximum和minimum尺寸,因為panel類的父類和子類可能不會使用絕對布局方式。
  小提示:我們都知道,swing是使用布局管理器(layout manager)來控制組件的位置,絕對布局的意思就是不使用布局管理器來控制組件的位置。(可以通過setlayout(null)方法來指定采用絕對布局方式)
  既然這樣,顯示指定的尺寸和位置將會被使用(可以通過setsize()和setlocation()方法)。當使用指定的布局管理器時,preferred,minimum和maximum尺寸可能會被使用。為了適應上面所有的情況,我們只要簡單的設置上面所提及的四個方法就可以了。
  現在,panel已經設置了適當的尺寸,我們可以通過重載paintcomponent()方法來繪制圖像:
public void paintcomponent(graphics g)
{
 g.drawimage(img, 0, 0, null);
}
  小提示:在這里我們重載了paintcomponent()方法,而不是paint()方法,這是很重要的一點,要不然子類將不會被重新繪制。
  現在來測試一下我們工作的成果,我們將自定義的panel添加到一個frame中,然后顯示該frame,參考下面的代碼示例:
  示例代碼2:
package com.demo.jcomponents;
import javax.swing.*;
/**
* 測試圖像panel組件
* @author xiazhi
*/
public class imagetest1
{
 public static void main(string[] args)
 {
  imagepanel panel = new imagepanel(createimageicon("images/background.png").getimage());
  jframe frame = new jframe("jfc/swing:創建以圖像為主題的組件");
  frame.getcontentpane().add(panel);
  frame.pack();
  frame.setvisible(true);
 }
 protected static imageicon createimageicon(string path)
 {
  java.net.url imgurl = imagetest1.class.getresource(path);
  if (imgurl != null)
  {
   return new imageicon(imgurl);
  }
  else
  {
   system.err.println("不能找到指定文件: " + path);
   return null;
  }
 }
}
  程序運行后,顯示效果如下:
  第二步 創建圖像label
  現在背景的繪制工作已經完成了。接下來要將重點轉移到標簽"activate reactor"的制作上了。這里僅僅是將一個靜態的圖像放置在背景的合適位置上。我們當然可以使用另外一個imagepanel實例來實現這部分功能,但是由于"activate reactor"這個幾個文字實際上僅僅是一個標簽,所以我們創建了另外一個子類imagelabel,參考下面的代碼示例:
  示例代碼3:
package com.demo.jcomponents;
import javax.swing.*;
/**
*圖像標簽
* @author xiazhi
*/
public class imagelabel extends jlabel
{
 /**
 * 構造函數
 *
 * @param img 圖像對象
 */
 public imagelabel(string img)
 {
  this(new imageicon(img));
 }
 /**
 * 構造函數
 *
 * @param icon 圖像圖標對象
 */
 public imagelabel(imageicon icon)
 {
  //設置標簽圖標
  seticon(icon);
  //設置標簽圖標和文本之間的間隔
  seticontextgap(0);
  //設置邊框
  setborder(null);
  //設置文本
  settext(null);
  setsize(icon.getimage().getwidth(null), icon.getimage().getheight(null));
 }
}
  與imagepanel類似,我們需要將label的尺寸與圖像的尺寸相匹配。這里我們只需要調用setsize()方法即可,因為label自己會處理其它的設置。接下來,將圖標設置為我們指定的圖像,這樣label自己會處理圖像的繪制。通過設置文本間隔為0,以及設置邊框為空,設置文本為空,將會去除圖像的任何額外的空間,將會使label與背景完美的嚙合在一起。setopaque(false)方法告訴label不要自己繪制背景。如果用圖像填充label,那么問題不大,但如果圖像中含有透明區域(大部分png類型的圖像都會這個樣子),那么在透明區域背景將會顯示出來。
  現在來測試一下我們的成果吧!在上面測試的基礎上添加了一個label,參考下面的代碼示例:
  示例代碼4: 
imagelabel label = new imagelabel(createimageicon("images/reactor.png"));
//定位label
label.setlocation(29, 37);
//為label增加提示信息
label.settooltiptext("看到了嗎?");
//將label添加到panel中
panel.add(label);
  程序運行后,顯示效果如下所示:
  第三步 創建圖像button
  接下來是創建定制的button。由于button具有翻轉特性和狀態特性,所以繪制button需要一些技巧。我們再次創建了一個jbutton類的子類,參考下面的代碼示例:
  示例代碼5:
package com.demo.jcomponents;
import java.awt.*;
import javax.swing.*;
/**
* 圖像按鈕
* @author xiazhi
*/
public class imagebutton extends jbutton
{
 /**
 * 構造函數
 *
 * @param img 圖像實例
 */
 public imagebutton(string img)
 {
  this(new imageicon(img));
 }
 /**
 * 構造函數
 *
 * @param icon 圖像圖標
 */
 public imagebutton(imageicon icon)
 {
  //設置圖標
  seticon(icon);
  //設置空白間距
  setmargin(new insets(0, 0, 0, 0));
  //設置文本與圖標之間的間隔
  seticontextgap(0);
  //指定是否繪制邊框
  setborderpainted(false);
  //設置邊框
  setborder(null);
  //設置文本
  settext(null);
  setsize(icon.getimage().getwidth(null), icon.getimage().getheight(null));
 }
}
  這段代碼幾乎與前面定制顯示的jlabel的代碼完全一樣。唯一的不同之處在于增加了setmargin()和setborder()方法的調用。大多數l&f使用邊框(border)和邊界(margin)來指明button是否已經被選取中。因為label不能被選取擇,所以沒有上述方法。不管怎么樣,我們只要將這兩個屬性關閉就可以了。 
  現在來測試一下我們的成果吧!在上面測試的基礎上添加了一個button,參考下面的代碼示例:
  示例代碼6:
final imagebutton button = new imagebutton(createimageicon("images/button.png"));
//定位button
button.setlocation(60,74);
//將button添加到panel中
panel.add(button);
  程序運行后,顯示效果如下所示:
  現在button已經添加到panel中,剩下的只要將翻轉和其它狀態屬性添到到button中就可以了。幸運的是,這些工作不需要我們在子類中添加任何新的代碼。jbutton已經提供了通過圖像來表征翻轉、按下、選擇、失效、失效選擇等屬性。我們只需要使用通常的set方法來添加各種狀態變量,參考下面的代碼示例:
button.setpressedicon(createimageicon("images/button-down.png"));
button.setrollovericon(createimageicon("images/button-over.png"));
button.setselectedicon(createimageicon("images/button-sel.png"));
button
.setrolloverselectedicon(createimageicon("images/button-sel-over.png"));
button.setdisabledicon(createimageicon("images/button-disabled.png"));
button
.setdisabledselectedicon(createimageicon("images/button-disabled-selected.png"));
  添加上述代碼后,再次運行程序,顯示效果如下所示:

選擇 

未選擇
  在這里我們使用了帶有光圈的圖像來表示button被選中,將圖像模糊化來表示button被禁用,圖像中間的矩形條用來表示button被選中的狀態,除了有顏色的改變外,還有發光的效果。
  為了完整的演示所有的狀態,我們在button的下部增加了一個標準的jcheckbox,通常情況下,它將會繪制一個灰色的背景(或者是帶條紋的背景在mac機上),我們調用setopaque(false)方法來強制要求它不要繪制。當父類沒有使用布局管理器時,調用checkbox.setsize(checkbox.getpreferredsize())方法是必須的,這樣可以使checkbox獲得合適的尺寸,就像本文示例中的情形:
  示例代碼7:
final jcheckbox checkbox = new jcheckbox("disable");
checkbox.setlocation(70, 150);
//強制要求checkbox不要繪制自己的背景
checkbox.setopaque(false);
//設置checkbox的尺寸
checkbox.setsize(checkbox.getpreferredsize());
//添加到panel中
panel.add(checkbox);
//添加事件監聽器
checkbox.addactionlistener(new actionlistener()
{
 public void actionperformed(actionevent evt)
 {
  button.setenabled(!checkbox.isselected());
 }
});
  到此示例程序已經完整了,程序運行后,整體的顯示效果如下所示:
  結束語
  至此,我們創建了完全定制的基于圖像的組件,jfc/swing有著龐大的結構,如何更好的去理解,理清組件之間的微妙關系,是學好swing的關鍵。中國最大的web開發資源網站及技術社區,