IFrame 是一種應用程序窗口體系結構,它可以有自定義顏色、自定義邊框、 自定義形狀、自定義組件、甚至透明性。有了 JFrame,就不需要在應用程序中總 是使用乏味的、老的 JFram 了。在本文中,IT 專家 Michael Aberethy 介紹了 IFrame 類,並展示了如何用它將標准 JFrame 應用程序窗口立刻轉換為先進的 IFrame。
自 Java 1.0 開始,UI 開發人員就一直在尋找自定義應用程序窗口的方法。 在引入了 Swing 後,這個問題愈發突出了,因為開發人員可以創建具有令人驚歎 的更復雜的、更高級 widget,但是卻受到它所在的 Frame 或者 JFrame 和它們 的特定於操作系統的外觀的限制。常常可以看到應用程序在框架邊框內部看起來 很好,但是 Microsoft Windows 的藍色標題欄卻大剎風景的情況。更糟的是,應 用程序有 70 年代的 Motif 外觀,但卻使用了 Microsoft Windows 的顏色光滑 漸變的標題欄。
JFrame 的外觀問題
如圖 1 所示的這些外觀問題,展現了 UI 開發人員所面臨的許多問題:應用 程序在各個平台上看起來是不一樣的,因為 JFrame 的屬性(標題欄顏色、邊框 、形狀等)無法使用公共外觀,它們是特於定操作系統的。
圖 1. JFrame 的外觀是如何變化的
Metal 外觀
Motif 外觀
Windows 外觀
解決方案看起來很很明顯:一個獨立於操作系統的框架,它具有 JFrame 提供 的所有功能——注冊到 OS 窗口管理器、調整大小/重繪管理、最大化/最小化/恢 復,同時又可以設置這些組件的外觀。Java 1.3 提供了 JFrame 的功能,但犧牲 了定制能力。
在 Java 1.4 中,Sun 在 Frmae 類中引入了一個新的 setUndecorated() 函 數,它走到了另一個極端——它可以自定義框架中的所有內容,但是缺少了 Java 1.3 中大多數特定於操作的好處。
IFrame 類是對這個問題的期待已久的解決方案。通過彌合這兩個問題之間的 距離,IFrame 提供了一個使用簡單、同時又很強大的解決方案,可以根據需要對 框架進行或多或少的自定義,同時不會犧牲使用 JFrame 時所習慣的任何特定於 操作系統的功能。不用再守著同樣的陳舊的藍色標題欄、同樣的陳舊的三個窗口 按鈕和同樣的陳舊的斜面邊框。雖然對於普通應用程序來說,JFrame 仍然很有用 ,但是有些應用程序需要比它所能提供的更多的功能,如:
所有具有自定義外觀的應用程序。
所有用於體現公司的某種品牌形象的應用程序。
所有需要對其窗口具有更多控制的應用程序。
IFrame 通過提供一個容易使用的 API 而突破了 JFrame 的限制,這個 API 可以生成具有任何狀態的框架、可以有任何顏色和狀態的標題欄、可用於更多功 能的標題欄按鈕、具有任何顏色和大小的邊框、按鈕旁邊的組件和標題欄中的標 簽及甚至是透明性。
本文介紹 IFrame 框架。首先描繪這個體系結構中的每一個類及其作用。當然 ,學習如何使用 IFrame 的最好方法是使用例子,所以我舉了四個使用 IFrame 的應用程序的例子,它們具有不同的復雜性。閱讀了本文及學習 IFrame 體系結 構後,應當能夠讓應用程序窗口從乏味窗口(圖 2)變為炫目窗口(圖 3)。
圖 2. 使用 IFrame 之前
圖 3. 使用 IFrame 之後
IFrame 體系結構
IFrame 體系結構非常容易和直觀,這可以從圖 4 展示的類圖中看出來。用於 作為應用程序窗口的主類是 IFrame 。IFrame 包含類 IBorderPanel 的一個到多 個實例,這個類的子類包括 IWindowTitleBar 和 IContentPane 。 IBorderPanels 通過觸發 WindowChangeEvents 與 IFrame 通信,IFrame 對它進 行處理並作出響應。
通過分析體系結構中每一個組件,就會清楚為了定制自己的 IFrame,需要在 什麼地方和什麼時候改寫默認功能。
圖 4. IFrame 體系結構
IFrame
IFrame 是用於創建自定義框架的主類。除了從 IFrame 的父類 JFrame 繼承 的方法外, IFrame 還增加了幾個 public 方法,可以用來改變 框架的基本外觀 組件。
IFrame 的默認行為與 JFrame 一樣,所以 JFrame 和默認 IFrame 可以互換 。不過,通過調用 IFrame 中可以使用的幾個 public 方法,可以只用幾行代碼 迅速改變窗口的外觀。
注:在 JFrame 對應的 getContentPane() 和 setContentPane() 的位置上應 當使用 IFrame 的 getIContentPane() 和 setIContentPane() 方法。在 IFrame 中添加的所有組件都應當調用 myIFrame.getIContentPane().add() 。用 getContentPane() 在 IFrame 中添加組件或者用 setContentPane() 設置內容窗 格會導致不可預測的、並且很可能是錯誤的行為。
IBorderComponent
IBorderComponent 是在 IFrame 中加入的所有可以控制 Windows 的位置和調 整大小的 JPanel 的父類。乍看之下,最可能的子類是 IWindowTitleBar 和 IContentPane ,因為它們看起來是惟一進行調整大小和移動的類。不過在當前的 應用程序中,有許多是用應用程序窗口內的組件調整應用程序窗口大小或者移動 它的。例如,所有 Microsoft Office 應用程序現在都在應用程序窗口右下角有 一個小的 widget,可以用來調整大小(可以去自己試一試)。所以如果設計一個 包含所有類型的調整大小和移動窗口的 widget,那麼所要做的就是繼承 IBorderComponent ,這些 widget 就會具有與用 IWindowTitleBar 和 IContentPane 得到的同樣的移動和調整大小的能力。
IWindowTitleBar
自定義框架的大部分工作都是在 IWindowTitleBar 中進行的。與 IFrame 一 樣, IWindowTitleBar 有許多 public 方法,不用繼承它或者自己編寫方法就可 以改變標題欄的屬性。雖然用這些方法可以容易地對外觀進行改變,但是所有高 級的設計都應當繼承 IWindowTitleBar 以使用 paintComponent() ,這個方法可 以做出更復雜的標題欄。(關於復雜的標題欄以及它是如何繼承 IWindowTitleBar 的內容,請參閱下面 例 3。)
除了改變標題欄的背景顏色, IWindowTitleBar 還提供了許多功能,可以用 來控制標題欄中的 IWindowButtons 。在默認情況下, IWindowTitleBar 包含 Microsoft Windows 應用程序中可以看到的三個標准窗口按鈕,但是可以容易地 刪除它們或者添加自己的具有不同功能的窗口按鈕。可以改變按鈕的大小和顏色 ,如果創建高級的窗口按鈕,那麼可以繼承 IWindowButton 以繪制所希望的任何 形狀和顏色方案(盡管改變 JButton 的外觀超出了本文的范圍)。
也許自定義 IWindowTitleBar 的最有技巧性的方面是管理邊框。在默認情況 下,窗口標題欄是標准的矩形邊框,但是如果創建一個具有非標准邊框的復雜標 題欄(請參閱 例 4),那麼必須繼承 IWindowTitleBar 並覆蓋 isMouseOnBorder() 和 isInsideTitleBar() ,以自己管理邊框。
IContentPane
IContentPane 提供了在其中加入所有應用程序組件的基本容器,很像 JFrame 中的 JFcontentPane。 因為它也繼承了 IBorderComponent ,所以在默認情況下 它也管理自己的邊框。 IContentPane 邊框的默認實現也是矩形。要想得到非矩 形的復雜邊框,需要繼承 IContentPane 並通過 重載 isMouseOnBorder() 自己 處理邊框。
IWindowButton
IWindowButton 提供了出現在標准 Microsoft Windows JFrame 標題欄中的三 個按鈕(最小化、還原和關閉)的默認實現,但是它們還為想要在標題欄中創建 和添加的所有自定義窗口按鈕提供了基類。如果希望在標題欄中加入第四個按鈕 (如果認為自己比 Microsoft 更了解情況),那麼可以繼承 IWindowButton 並 重載 paintComponent() 。不過,對於創建自定義按鈕的介紹不在本文的范圍之 內。
WindowChangeEvent/WindowChangeListener
Swing 為窗口中會發生的幾乎所有事情提供了事件。有大約 15 種事件和處理 窗口事件的相應方法。但是,就算有了所有這些事件,Swing 也沒有包括窗口可 以生成的所有事件,最明顯的是特定於操作系統的事件。最後,使用 WindowChangeEvent 和 WindowChangeListener 類,可以確保接收窗口會發生的 所有事件。
WindowChangeEvent 處理所有窗口變化的情況(因而類的名字變化)。它可以 改變其大小、在屏幕上定位、恢復狀態或者最小化/最大化狀態。將這五個事件加 上 15 種已有的事件,就包括了所有基本情況。現在窗口中發生的所有事情都可 以掌握了。
例子
現在可以編寫幾行代碼並改變應用程序窗口的整個外觀、親自體驗 IFrame 的 強大功能了。記住,IFrame 可以很簡單,也可以很復雜,完全取決於您的需要。
在這一節,我將完成幾個展示開始使用 IFrame 所需要完成的基本步驟的例子 。學習這些例子並在自己的計算機中運行它們,會看到僅憑閱讀說明或者 API 所 想像不到的效果。運行所有四個例子並分析每個例子的代碼,我相信您將會理解 為什麼 IFrame 可以成為應用程序中一個強大的工具。
所有例子都包含在 com.ibm.iwt.examples 包中,可以從 參考資料 中下載這 個包,它們都有可以運行的 main() 方法。它們是用 JDK 1.4 編寫的。
例 1:默認 IFrame
為了保持 IFrame “向後兼容”,我讓 IFrame 的默認實現看上去與 JFrame 的完全一樣,如圖 5 所示:
圖 5. 默認 IFrame
因為 IFrame 不從本機操作系統中得到其信息,所以我只能選擇一種操作系統 進行模擬。默認的 IFrame 實現看起來就像在 Microsoft Windows 2000 中的 JFrame 一樣,我們就保持使用它了。如果在 Windows 2000 計算機中運行應用程 序,那麼將可以互換 JFrame 與 IFrame,不會有看得出來的差別。如果運行的不 是 Windows 2000 -- 那麼,第一個練習應用程序可以是模擬自己的操作系統。清 單 1 顯示了創建一個 IFrame 是多麼容易:
清單 1. IFrame 例 1 public TestApp1()
{
setTitle("Window");
}
是的,就是這麼容易(想象一下如果所有應用程序開發都這麼容易,那該會怎 樣)。
建議用法:在希望向後兼容 JFrame 時。
例 2: 改變默認顏色、邊框和大小
現在看一些更有意思的代碼。在這個例子中,我將標題欄框架周圍的邊框的背 景顏色改為紅色,改變窗口按鈕的顏色、還改變了標題欄和窗口按鈕的大小。圖 6 顯示了在例 2 中創建的 IFrame。
圖 6. 改變顏色、邊框和大小
僅就所說的這些改變,可以看出它們在 JFrame 中都是不可能的,但是用 IFrame 就可以很容易地實現。清單 2 顯示了如何創建例 2 中使用的 IFrame:
清單 2. IFrame 例 2 public TestApp2()
{
IWTUtilities.setBorderSize(new Insets(3,3,3,3));
setIContentPaneBorder(new LineBorder(Color.red, 3));
setTitleBarHeight(35);
setTitleBarBackground(Color.red);
setTitleBarButtonColors(Color.red, Color.white);
setTitleBarButtonSize(new Dimension(26, 26));
setTitle("Window");
}
這樣就行了。改變框架的外觀所要做的就是這些。盡管這只是 IFrame 的一個 基本的例子,只使用了六行代碼,但是我們完成了一些 UI 開發人員多年來一直 想要做的事情。這個基本的例子已經比當前使用的應用程序窗口中的 99% 都更先 進。
建議用法: 如果希望迅速改變框架的外觀,同時又不想使它與特定於操作系 統的框架有大的改變時使用。
例 3: 利用 IWindowTitleBar 的子類
如果希望做比顏色、大小和標題欄中的按鈕這樣的基本改變更多的事情,就必 須繼承 IWindowTitleBar 類以充分利用它提供的各種可能性。創建了子類後,就 可以對標題欄做很多新的操作了,包括更高級的繪制選項以及更強大的、在標題 欄中加入任何組件的能力。為什麼讓標題欄中的按鈕和標簽把自己限制住呢?加 上一直想要的 JTable 吧。只要調用 IFrame 中的 setTitleBar() ,就可以創建 一個應用程序開發史上最先進的標題欄子類,並在任何 IFrame 上使用它。圖 7 描繪了創建自定義窗口組件所可能產生的外觀:
圖 7. 創建自定義窗口組件
在這個例子中,通過建立 清單 2 中的框架,並用一個新的、動態的邊框取代 單調的、靜態的紅色標題欄,充分利用了所有這些新的可能性。可以從圖中看到 ,標題已經從左邊移到了中間,並使用了更有可讀性的字體。我用一個在左邊的 “關閉”按鈕取代右邊三個標准按鈕。最後,也許是最有創造性的,我在標題欄 的右邊增加了一個 JSlider,可以讓這個 IFrame 的用戶動態改變標題欄背景的 漸變色。清單 3 中的代碼片段顯示了將例 2 轉變為例 3 所需要的額外代碼。這 些對於 JFrame 來說是不可能的。
清單 3. IFrame 例 3 public TestApp3()
{
IWTUtilities.setBorderSize(new Insets(3,3,3,3));
getIContentPane().setBorder(new LineBorder(Color.red, 3));
setTitleBar(new TitleBar());
}
private class TitleBar extends IWindowTitleBar implements ChangeListener
{
private Color c = new Color(0,0,0);
private JSlider slider;
public TitleBar()
{
setPreferredSize(new Dimension(0, 26));
removeWindowDecorations();
addWindowButton(IWindowButton.CLOSE, SwingConstants.LEFT);
setWindowButtonColors(Color.RED, Color.WHITE);
addTitle(getTitle(), SwingConstants.CENTER, new Font ("Verdana", Font.BOLD, 14), Color.WHITE);
slider = new JSlider();
add(slider, new GroupFlowLayoutConstraints (SwingConstants.RIGHT, new Insets(3,3,3,3)));
slider.addChangeListener(this);
slider.setMaximum(255);
slider.setMinimum(0);
slider.setOpaque(false);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
PaintUtilities.paintGradient(g, 0, 0, getWidth(), getHeight(), c, Color.WHITE,
SwingConstants.HORIZONTAL);
}
public void stateChanged(ChangeEvent e)
{
c = new Color(slider.getValue(), 0, 0);
repaint();
}
}
分析創建這個 IFrame 的代碼,可以看到它不比 清單 2 中的代碼更復雜。不 過,出於下面兩個理由,我將所有代碼移到了 IWindowTitleBar 的子類中:
通過 重載 IWindowTitleBar 中的 paintComponent() 提供外觀更精致的標題 欄
加入動態改變標題欄背景顏色的 JSlider
因為可以在任何位置上添加任何 JComponent,所以在為標題欄創建新 widget 時可以盡情發揮想象力。對於在標題欄中創建新功能這方面來說,改變背景顏色 的 JSlider 只是冰山的一角。可以開發出許多在標題欄中使用的有創造性的自定 義組件。
建議用法: 適合使用 IWindowTitleBar 的子類的情況有:
希望在標題欄中創建更復雜的圖像,而不是一種單純的顏色
常常會希望動態改變框架的標題欄屬性,並且不希望每改變次它們時調用多個 函數
希望在標題欄中加入默認組件以外的其他組件
例 4:結合在一起並加上透明性
最後一個例子將其他例子結合到一起並加入了 IFrame 的最新特性——透明性 。這個例子是最復雜的,並且很好地體現了 IFrame 在用最少的工作創建具有出 色外觀的應用程序窗口方面的強大能力。圖 8 顯示了具有某種透明性的復雜應用 程序窗口,這種透明性使它區別其他應用程序窗口。
圖 8. 加入透明性
首先,讓我們介紹一下透明性。幾年前,Microsoft Windows 應用程序開始有 了標准矩形以外的框架。其中使用最多的就是 Windows Media Player,從那之後 ,使應用程序具有非矩形形狀就成了一種趨勢和很酷的事情。是的,Java 應用程 序一直沒有這種能力,並且在透明性方面總是差強人意,特別是當與本機繪制像 素交互時。
幸運的是,IFrame 改變了這種局面,可以開發具有透明性、甚至對於本機繪 制像素透明的應用程序窗口。IFrame 中的 setTransparent() 在指定的邊界內繪 制指定的組件透明性。在大多數情況下,組件將是 IWindowTitleBar 或 IContentPane 的子類。應當在子類的 paintComponent() 中調用 setTransparent() ,以使它可以用它下面的正確像素重繪。
最後提醒一下,繪制透明性速度相對來說是慢的,應當盡可能使透明區域相對 較小。
最後這個例子使用了 IFrame 的其他更高級的功能。從 清單 8 中可以看到, 標題欄不再是標准的矩形標題欄了。它是自定義的形狀,具有完全不同於矩形的 邊框。因此,在所創建的 IWindowTitleBar 子類中,必須 重載 isMouseOnBorder() 和 isInsideTitleBar() 方法,以使標題欄在繪制光標時具 有正確的行為,並可以調整大小。清單 4 顯示了生成例 4 中看到的應用程序窗 口所需要的代碼。
清單 4. IFrame 例 4 public TestApp4()
{
setTitle("Window");
IWTUtilities.setBorderSize(new Insets(0,7,7,7));
IWTUtilities.setDiagonalSize(20);
getIContentPane().setBorder(new AppBorder());
getIContentPane().setBackground(new Color(255, 255, 102));
setTitleBar(new TitlePanel());
}
private class TitlePanel extends IWindowTitleBar
{
public TitlePanel()
{
setPreferredSize(new Dimension(800,35));
setFont(new Font("Verdana", Font.BOLD, 22));
removeWindowDecorations();
}
protected boolean isInsideTitleBar(int x, int y)
{
if (x < (int)getWidth()*.1 || x > (int)getWidth()*.9)
return false;
return true;
}
protected void isMouseOnBorder(int x, int y)
{
if (y > 10 && y > 16 && ! isInsideTitleBar(x, y))
isMouseOnBorder = true;
else
isMouseOnBorder = false;
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// ... paint code here
setTransparent(this, g, 0, 0, w+1, 10);
PaintUtilities.paintDropShadow(g, (int)(w*.1), 0, (int)(w*.8), 27);
Color c1 = new Color(67, 118, 135);
Color c2 = new Color(105, 152, 199);
PaintUtilities.paintGradient(g, (int)(w*.1), 0, (int)(w*.9), 14, c1, c2);
PaintUtilities.paintGradient(g, (int)(w*.1), 14, (int)(w*.9), 13, c2, c1);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint (RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.white);
int strW = SwingUtilities.computeStringWidth(g.getFontMetrics (), getTitle());
int strH = g.getFontMetrics().getMaxAscent();
g2.drawString(getTitle(), w/2-strW/2, h-strH/2);
g2.setRenderingHint (RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF); }
private void drawBorder(Graphics g, int x, int y, int w, int h)
{
g.drawLine(x, 10+y, x, h);
g.drawLine(x, 10+y, w-x, 10+y);
g.drawLine(w-x, 10+y, w-x, h);
}
}
private class AppBorder extends AbstractBorder
{
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
// ... paint border
}
}
建議用法:創建一個非百分之百矩形的框架是當前 UI 開發中的一種趨勢(僅 就 Windows XP 而言)。使用 IFrame 後,Java 應用程序就不會落伍了。使用透 明性並 重載 IWindowTitleBar 及其所有高級函數,就可以創建具有非常精致外 觀的框架,可以作為整個公司應用程序的默認框架。先進的功能使 UI 開發人員 可以開發出這樣的框架,它可以使用戶自動與某家公司關聯到一起(而不只限於 那種使用戶自動關聯到 Remond,Washington 的某家公司的框架)。
結束語
通過讓 UI 開發人員可以完全控制他們的框架的功能 和和外觀,IFrame 最終 彌補了 Java 開發中的缺撼。它使 UI 開發人員可以創建只改變標題欄字體的簡 單 IFrame,也可以創建改變整個公司外觀的復雜 IFrame。IFrame 的好處在於開 發人員容易使用。它提供了開發人員改變框架所需要的所有功能,而且還非常易 於擴展,使開發人員可以只改變需要改變的地方,而不會干擾其他默認行為。
從我們完成的這些例子中可以看到,框架可能有的外觀只受我們的想象力的限 制。我相信在閱讀過程中,您會在腦子裡產生一些想法,希望讀過本文後,可以 用 IFrame 很快地將這些想法落實到屏幕上。
我很想知道您開發出了什麼樣的 IFrame,所以請將您得到的任何 IFrame 的 屏幕快照發給我。真想看到其他人是如何利用 IFrame 的。
本文配套源碼