Swing 工具包提供各種用於創建用戶界面的工具和幾乎令人眼花缭亂的選項這些選項用於在程序生存期間修改界面小心地使用這些功能可以導致界面能夠適應用戶的需要並簡化交互過程粗心地使用同樣的功能可以導致非常混亂或徹底不可用的程序本文介紹動態 UI 的技術和體系並提供有關構建有效的界面的幫助您將修改隨 Sun JDK 一起提供的基於 SwingSet 示例應用程序的源代碼;此應用程序的 UI 使用許多動態的特性並且可以作為理解它們的極好的起點
禁用小部件
動態 UI 的最簡單形式是使不可用的菜單項或按鈕變灰的 UI禁用 UI 小部件與禁用所有小部件的方法都是相同的setEnabled() 函數是 Component 類的一個功能清單 顯示了禁用按鈕的代碼
清單 禁用按鈕
buttonsetEnabled(false);
正如您看到的十分簡單關鍵問題是何時應該 啟用或禁用一個按鈕通常的設計決策是當按鈕不可用時禁用它例如當一個文件從上一次保存以來還沒有被修改時很多程序禁用 Save 按鈕(以及任何相應的菜單項)
關於禁用按鈕的重要警告是要記住在適當的時候重新啟用它們例如如果在單擊按鈕和按鈕的動作完成之間有一個確認步驟即使確認失敗也應該重新啟用按鈕
調整范圍
有時應用程序需要動態地調整數值小部件的范圍例如 Spinner 或者 Slider這可能比它看起來要復雜許多特別是 Slider 有二級功能 —— 刻度刻度間隔和標簽 —— 這些可能需要隨著范圍的調整而加以調整以避免災難發生
SwingSet 示例沒有進行任何一項調整所以您需要通過把 ChangeListener 連接到一個可以修改其他滑塊的滑塊來修改它輸入新的 SliderChangeListener 類 如清單 所示
清單 更改滑塊的范圍
class SliderChangeListener implements ChangeListener {
JSlider h;
SliderChangeListener(JSlider h) {
thish = h;
}
public void stateChanged(ChangeEvent e) {
JSlider js = (JSlider) egetSource();
int i = jsgetValue();
hsetMaximum(i);
hrepaint();
}
}
當創建第三個水平滑塊時(最初示例中的滑塊在每個單位處帶有標記在 和 等處帶有標簽) 另外還創建了一個新的 SliderChangeListener它把滑塊作為構造器參數傳遞當創建第三個垂直的滑塊(范圍從 到 )時新的 SliderChangeListener 作為變更監聽器添加到它這大致按預期的那樣工作調整垂直滑塊改變了水平滑塊的范圍
不幸的是刻度和標簽根本不能很好地工作當范圍變得不是太大時每五個刻度處的標簽能正確地工作但是刻度 處的額外標簽很快成為一個可用性問題如圖 所示
圖 一起運行的標簽
更新刻度和標簽
明顯的解決方案是只要滑塊的最大值被更新就在水平滑塊上簡單地設置刻度間隔如清單 所示
清單 設置刻度間隔
// DOES NOT WORK
int tickMajor tickMinor;
tickMajor = (i > ) ? (i / ) : ;
tickMinor = (tickMajor > ) ? (tickMajor / ) : tickMajor;
hsetMajorTickSpacing(tickMajor);
hsetMinorTickSpacing(tickMinor);
hrepaint();
目前清單 是正確的但是它沒有引起畫在屏幕上的標簽的任何變化必須使用 setLabelTable() 分別設置標簽 添加額外一行修復它
hsetLabelTable(hcreateStandardLabels(tickMajor));
這仍然出現在刻度 處存在最初設置的標簽這樣的錯誤當然本來的意圖是想在滑塊的最右端始終有一個標簽可以通過刪除舊的標簽(在設置新的最大值之前)然後添加一個新的標簽達到這一目的此代碼 幾乎 可以工作
清單 替換標簽
public void stateChanged(ChangeEvent e) {
JSlider js = (JSlider) egetSource();
int i = jsgetValue();
// clear old label for top value
hgetLabelTable()remove(hgetMaximum());
hsetMaximum(i);
int tickMajor tickMinor;
tickMajor = (i > ) ? (i / ) : ;
tickMinor = (tickMajor > ) ? (tickMajor / ) : tickMajor;
hsetMajorTickSpacing(tickMajor);
hsetMinorTickSpacing(tickMinor);
hsetLabelTable(hcreateStandardLabels(tickMajor));
hgetLabelTable()put(new Integer(i)
new JLabel(new Integer(i)toString() JLabelCENTER));
hrepaint();
}
如果我已經告訴過您一次那麼我就已經告訴您兩次了
我使用幾乎 的意思是雖然清單 中的代碼刪除了刻度 處的標簽但是它沒有在刻度 i 處添加新標簽;相反只能看到間隔為 tickMajor 的標簽首先此解決方法相當令人討厭
清單 強迫顯示更新
hsetLabelTable(hgetLabelTable());
這個看起來無意義的操作實際上有重大的作用每當設置標簽表時就生成滑塊的標簽沒有為了修改對表進行特殊回調所以添加到表中的新值不必產生效果;很顯然清單 中的空操作具有使 Swing 知道它必須更新顯示的副作用(以免您認為這是我自己發明的請注意最初的 SwingSet 代碼包括這樣一個調用)
這只留下了一個問題標簽出現在滑塊的末端這個非常合理的期望有時使兩個標簽互相直接相鄰乃至重疊如圖 所示
圖 滑塊末端的重疊標簽
很多解決此問題的方法都是可行的編寫自己的代碼以使用值來填充標簽表並停止以前的序列以便序列中的最後標簽與滑塊的末端有一些隔離我將把這個作為作業留給您
在許多情況下為了啟用和禁用菜單項而限制菜單修改是很實際的此方法容易受到用於禁用項的常規警告的影響避免由於偶然地禁用重要項而使程序處於不可用狀態
添加或刪除菜單項或子菜單也是可能的修改 JMenuBar 沒有這麼容易;沒有從工具欄上刪除和替換單個菜單的接口如果您想修改工具欄(而不是向最右端添加菜單)需要制作一個新的工具欄並用它替換舊的工具欄
修改單個菜單會立即生效;您不必在將菜單連接到工具欄或另一個菜單之前構建一個菜單當需要修改菜單選項的選擇時最容易的方法是修改選定的菜單您可能仍然想添加或刪除完整的菜單並且這麼做並不是特別難清單 顯示一個將菜單插入到菜單條中給定索引前的方法的簡單示例此示例假定要替換的 JMenuBar 連接到 JFrame 對象但是任何能使您獲得和設置菜單條的東西的工作方式都是一樣的
清單 把一個菜單插入到菜單條中
public void insertMenu(JFrame frame JMenu menu int index) {
JMenuBar newBar = new JMenuBar();
JMenuBar oldBar = framegetJMenuBar();
MenuElement[] oldMenus = oldBargetSubElements();
int count = oldBargetMenuCount();
int i;
for (i = ; i < count; ++i) {
if (i == index)
newBaradd(menu);
newBaradd((JMenu) oldMenus[i]);
}
framesetJMenuBar(newBar);
}
上面的代碼我不是開始時就試圖編成這樣;這是最終的版本已經很好地修復過了所以它能夠運行並反映一些有趣的怪事初看起來實現此功能的明顯方法似乎應該是使用 getComponentAtIndex()但是這種方法已經受到了反對幸運的是getSubElements() 已經足夠好為 newBaradd() 而進行到 JMenu 的強制類型轉換可能是安全的但是我不喜歡這樣getSubElements() 接口不僅對菜單條而且對菜單進行操作菜單可能具有幾種類型的子元素JMenu 是可以添加到 JMenuBar 的惟一元素所以必須把元素強制轉換為 JMenu 以把它傳遞到 JMenuBaradd() 方法不幸的是如果將來的 API 修訂版允許將除 JMenu 類型之外的元素添加到 JMenuBar就不再需要把返回的元素強制轉換 JMenu了
清單 中的代碼反映了另外一個相當微妙的界面怪事;菜單數必須提前緩存起來當把菜單添加到新的菜單條時它們從舊的菜單條中被刪除雖然看起來相似但是清單 中的代碼不能工作因為循環提前結束了:
清單 循環結束太早
// DOES NOT WORK
for (i = ; i < oldBargetMenuCount(); ++i) {
if (i == index)
newBaradd(menu);
newBaradd((JMenu) oldMenus[i]);
}
清單 中的循環只復制一半數量的菜單例如如果開始菜單條上有 個 菜單它復制前面的兩個菜單復制完第一個菜單以後 i 的值為 並且 getMenuCount() 返回 ;在復制完第二個菜單以後i 的值為 並且 getMenuCount() 返回 因此循環結束我沒有找到任何介紹通過向菜單條添加菜單從而從另一個菜單條刪除菜單這樣的特性的文檔因此可能不是有意這樣但是它很容易達到這個目的
從菜單條刪除菜單稍微容易一些;只是把所有其他的菜單從舊的菜單條復制到新的菜單條就完成了刪除菜單很容易!
如果界面使用了很多動態菜單更新創建一組菜單條並在它們之間切換而不是一直動態地更新它們也許會更好一些但是如果如此快地改變菜單可能會使用戶完全發瘋
勘誤在本文的草稿階段我忽略了 JMenuBar 類的繼承方法的列表其實它有 remove 和 add 方法可以用來在指定的索引處進行刪除和插入另外一個教訓是查看繼承的方法而不僅僅是特定於類的方法
調整窗口大小
所幸的是對於大多數情況窗口大小調整是自動進行的但是需要考慮調整大小產生的一些影響在非常小的窗口中按鈕條菜單條和類似功能可能變成有問題的管理程序自身的圖形面板需要響應調整大小事件讓 Swing 處理對 UI 元素的包裝但是要密切注視組件的大小;不要獲取一次組件的尺寸然後就一直使用這些值
更微妙的地方是一些設計決策(例如滑塊上刻度的密度)可能被適度地更新以響應窗口大小調整事件 像素寬度的滑塊不能具有像 像素寬度的滑塊那樣多的可讀標簽您可能想通過添加全新的有用功能來讓 UI 更進一步用在大型顯示器上
但是在多數情況下可以忽略窗口大小調整您不應該做的是不必要地阻止或重寫它布局代碼中的窗口一側的便捷工具不是必需的最小的窗口大小可能是無可厚非的但是要讓人們能把窗口調整到他們所需要的大小
一般原則
Swing 工具包在 UI 設計方面提供了很大的靈活性如果小心地使用動態更新界面的選項能夠極大地簡化該界面;例如只有應用菜單的選項時用戶才能容易地顯示菜單
不幸的是一些 API 的特性可能使此方法產生一些離奇的行為並且副作用和相互影響並不總是像您期望的那樣記錄下來如果您有使用動態界面的想法就要准備在調試上花費一些額外的時間您可能從 Swing 庫的困境中走出來並發現自己需要處理出人意料的行為和/或 bug
不要讓缺乏明顯的實現讓您氣餒像本文的 JMenuBar 示例所顯示的即使當 API 不支持某個任務時您也能自己實現它雖然有一點間接
盡量不要走極端當動態 UI 讓用戶清楚它們的固有限制時它們才能最好地發揮作用理想的情況是用戶甚至可能不會注意到界面變化如果他們能夠使用程序的 Object 菜單的惟一時刻是當他們使某個對象被選擇時那麼其余的時間他們將不會介意不存在該菜單
另一方面如果存在這種可能性用戶不能猜測出一個選項不可用的原因這時讓用戶嘗試操作並獲得包含信息的消息也許會更好這對於一些操作尤其重要如果保存選項被禁用而我想保存數據那麼這不會發生作用程序可能認為已經保存了數據但是為什麼不讓我無論如何都保存它呢?如果存在不能保存文件的特殊原因我可能想知道是什麼原因
盡管研究了很多年界面設計在很多方面仍舊是一個較新的領域只進行了很少的試驗動態改變 UI 是一個很好的特性能夠使 UI 更清晰更簡單和反應更迅速添加動態特性需要從幾分鐘的工作到大量時間的花費不等
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26042.html