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:
構造函數使用Image的實例作為參數,并將繪制的圖像保存在變量img中以備以后使用。接著調用setSize()和setPreferredSize()方法,并以圖像的尺寸作為參數。這樣可以確保Panel的尺寸與圖像的尺寸完全一致。接下來的操作非常重要,必須顯示地指定Panel的preferred、maximum和minimum尺寸,因為Panel類的父類和子類可能不會使用絕對布局方式。
小提示:我們都知道,Swing是使用布局管理器(Layout Manager)來控制組件的位置,絕對布局的意思就是不使用布局管理器來控制組件的位置。(可以通過setLayout(null)方法來指定采用絕對布局方式)
既然這樣,顯示指定的尺寸和位置將會被使用(可以通過setSize()和setLocation()方法)。當使用指定的布局管理器時,preferred,minimum和maximum尺寸可能會被使用。為了適應上面所有的情況,我們只要簡單的設置上面所提及的四個方法就可以了。
現在,Panel已經設置了適當的尺寸,我們可以通過重載paintComponent()方法來繪制圖像:
小提示:在這里我們重載了paintComponent()方法,而不是paint()方法,這是很重要的一點,要不然子類將不會被重新繪制。
現在來測試一下我們工作的成果,我們將自定義的Panel添加到一個Frame中,然后顯示該Frame,參考下面的代碼示例:
示例代碼2:
程序運行后,顯示效果如下:
第二步 創建圖像Label
現在背景的繪制工作已經完成了。接下來要將重點轉移到標簽"Activate Reactor"的制作上了。這里僅僅是將一個靜態的圖像放置在背景的合適位置上。我們當然可以使用另外一個ImagePanel實例來實現這部分功能,但是由于"Activate Reactor"這個幾個文字實際上僅僅是一個標簽,所以我們創建了另外一個子類ImageLabel,參考下面的代碼示例:
示例代碼3:
與ImagePanel類似,我們需要將Label的尺寸與圖像的尺寸相匹配。這里我們只需要調用setSize()方法即可,因為Label自己會處理其它的設置。接下來,將圖標設置為我們指定的圖像,這樣Label自己會處理圖像的繪制。通過設置文本間隔為0,以及設置邊框為空,設置文本為空,將會去除圖像的任何額外的空間,將會使Label與背景完美的嚙合在一起。setOpaque(false)方法告訴Label不要自己繪制背景。如果用圖像填充Label,那么問題不大,但如果圖像中含有透明區域(大部分PNG類型的圖像都會這個樣子),那么在透明區域背景將會顯示出來。
現在來測試一下我們的成果吧!在上面測試的基礎上添加了一個Label,參考下面的代碼示例:
示例代碼4:
程序運行后,顯示效果如下所示:
第三步 創建圖像Button
接下來是創建定制的Button。由于Button具有翻轉特性和狀態特性,所以繪制Button需要一些技巧。我們再次創建了一個JButton類的子類,參考下面的代碼示例:
示例代碼5:
這段代碼幾乎與前面定制顯示的JLabel的代碼完全一樣。唯一的不同之處在于增加了setMargin()和setBorder()方法的調用。大多數L&F使用邊框(Border)和邊界(Margin)來指明Button是否已經被選取中。因為Label不能被選取擇,所以沒有上述方法。不管怎么樣,我們只要將這兩個屬性關閉就可以了。
現在來測試一下我們的成果吧!在上面測試的基礎上添加了一個Button,參考下面的代碼示例:
示例代碼6:
程序運行后,顯示效果如下所示:
現在Button已經添加到Panel中,剩下的只要將翻轉和其它狀態屬性添到到Button中就可以了。幸運的是,這些工作不需要我們在子類中添加任何新的代碼。JButton已經提供了通過圖像來表征翻轉、按下、選擇、失效、失效選擇等屬性。我們只需要使用通常的set方法來添加各種狀態變量,參考下面的代碼示例:
添加上述代碼后,再次運行程序,顯示效果如下所示:
在這里我們使用了帶有光圈的圖像來表示Button被選中,將圖像模糊化來表示Button被禁用,圖像中間的矩形條用來表示Button被選中的狀態,除了有顏色的改變外,還有發光的效果。
為了完整的演示所有的狀態,我們在Button的下部增加了一個標準的JCheckBox,通常情況下,它將會繪制一個灰色的背景(或者是帶條紋的背景在Mac機上),我們調用setOpaque(false)方法來強制要求它不要繪制。當父類沒有使用布局管理器時,調用checkbox.setSize(checkbox.getPreferredSize())方法是必須的,這樣可以使checkbox獲得合適的尺寸,就像本文示例中的情形:
示例代碼7:
到此示例程序已經完整了,程序運行后,整體的顯示效果如下所示:
結束語
至此,我們創建了完全定制的基于圖像的組件,JFC/Swing有著龐大的結構,如何更好的去理解,理清組件之間的微妙關系,是學好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的關鍵。