top
Loading...
JFC/Swing活學活用之創建自定義圖像組件
下載本文源代碼

引言

本文將講述如何應用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的關鍵。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗