隨心所欲學Java - Swing國際化,控件實時刷新觀察者方式實現

 之前說到要想看看Swing。不知道爲啥我第一個想到的問題是界面語言顯示的國際化問題。管他呢,既然隨心所遇學,想到了就實現來看看。

想要國際化,首先還是要有國際化資源文件。
 

 我們所要做的就是在切換語言的時候,從對應的資源文件中讀取值信息,顯示出來。

要實時響應語言的變化,可以採用JDK自帶的觀察者方式來實現。
 
  
這裏封裝了一個ObserverCenter,用於統一發送被觀察實例的變化狀態信息。
 
  1. /** 
  2.  * 觀察者中心,用於統一通知發送變更信 
  3.  *  
  4.  * @author lihzh 
  5.  * @date 2012-3-17 下午9:54:15 
  6.  */ 
  7. public class ObserveCenter extends Observable { 
  8.      
  9.     private static ObserveCenter observeCenter; 
  10.     private ObserveCenter(){} 
  11.      
  12.     public static ObserveCenter getInstance() { 
  13.         if (observeCenter == null) { 
  14.             observeCenter = new ObserveCenter(); 
  15.         } 
  16.         return observeCenter; 
  17.     } 
  18.      
  19.     /** 
  20.      * 發送變更消息, 相當於{@link #notifyChange(null)} 
  21.      *  
  22.      * @author lihzh 
  23.      * @date 2012-3-17 下午9:58:19 
  24.      */ 
  25.     public void notifyChange() { 
  26.         notifyChange(null); 
  27.     } 
  28.  
  29.     /** 
  30.      * 發送變更信息 
  31.      *  
  32.      * @param obj 
  33.      * @author lihzh 
  34.      * @date 2012-3-17 下午10:01:50 
  35.      */ 
  36.     public void notifyChange(Object obj) { 
  37.         setChanged(); 
  38.         notifyObservers(obj); 
  39.     } 
  40.  
LocaleHandler類用於設置區域信息,同時在設置區域信息後,發送通過ObserverCenter發送狀態改變通知。
 
  1. /** 
  2.  * 當前位置(國際化信息所用)管理類 
  3.  *  
  4.  * @author lihzh 
  5.  * @date 2012-3-17 下午4:26:43 
  6.  */ 
  7. public class LocaleHandler { 
  8.  
  9.     private static final Logger logger = LoggerFactory.getLogger(LocaleHandler.class); 
  10.      
  11.     public static final Locale DEFAULT_LOCAL = Locale.CHINA; 
  12.     private static Locale locale = DEFAULT_LOCAL; 
  13.  
  14.     static { 
  15.         ObserveCenter.getInstance().addObserver(new I18nUIHandler()); 
  16.         ObserveCenter.getInstance().addObserver(new ResourceBundleLoader()); 
  17.     } 
  18.  
  19.     /** 
  20.      * 獲取國際化區域信息 
  21.      *  
  22.      * @return 
  23.      * @author lihzh 
  24.      * @date 2012-3-17 下午9:32:13 
  25.      */ 
  26.     public static Locale getLocale() { 
  27.         return locale; 
  28.     } 
  29.  
  30.     /** 
  31.      * 設置國際化區域信息 
  32.      *  
  33.      * @param locale 
  34.      * @author lihzh 
  35.      * @date 2012-3-17 下午9:31:47 
  36.      */ 
  37.     public static void setLocale(Locale locale) { 
  38.         LocaleHandler.locale = locale == null ? DEFAULT_LOCAL : locale; 
  39.         logger.info("Current locale is: [{}]", LocaleHandler.locale.toString()); 
  40.         refresh(); 
  41.     } 
  42.  
  43.     /** 
  44.      * 設置國際化區域信息,相當於{@link #setLocale(DEFAULT_LOCAL)} 
  45.      *  
  46.      * @author lihzh 
  47.      * @date 2012-3-17 下午9:35:00 
  48.      */ 
  49.     public static void setLocale() { 
  50.         setLocale(DEFAULT_LOCAL); 
  51.     } 
  52.  
  53.     /** 
  54.      * 刷新國際化信息 
  55.      *  
  56.      * @author lihzh 
  57.      * @date 2012-3-17 下午9:31:58 
  58.      */ 
  59.     private static void refresh() { 
  60.         ObserveCenter.getInstance().notifyChange(); 
  61.     } 
  62.  
ResourceBundleLoader類,用於根據區域設置加載對應的國際化配置文件。該類實現了Observer接口。在Locale信息變化時,會收到通知,改變需要加載的國際化文件。
 
  1. /** 
  2.  * 國際化文件讀取類,提供靜態方法和靜態引用 
  3.  *  
  4.  * @author lihzh 
  5.  * @date 2012-3-17 下午4:07:24 
  6.  */ 
  7. public class ResourceBundleLoader implements Observer { 
  8.  
  9.     private static final String RESOURCE_CLASSLOADER_PATH_PREFIX = "i18n.i18n_"
  10.     private static String RESOURCE_CLASSLOADER_PATH_FULL; 
  11.     private static ResourceBundle resourceBundle; 
  12.  
  13.     /** 
  14.      * 讀取國際化文件中定義的值 
  15.      *  
  16.      * @param key 
  17.      * @return 
  18.      * @author lihzh 
  19.      * @date 2012-3-17 下午4:33:32 
  20.      */ 
  21.     public static String getValue(String key) { 
  22.         return resourceBundle.getString(key); 
  23.     } 
  24.  
  25.     /* 
  26.      * 當Locale環境變量改變時更改resourceBundle實例加載的國際化文件 
  27.      */ 
  28.     @Override 
  29.     public void update(Observable o, Object arg) { 
  30.         refreshBundle(); 
  31.     } 
  32.  
  33.     /** 
  34.      * 刷新讀取的Bundle信息 
  35.      *  
  36.      * @author lihzh 
  37.      * @date 2012-3-17 下午10:13:26 
  38.      */ 
  39.     private static void refreshBundle() { 
  40.         RESOURCE_CLASSLOADER_PATH_FULL = RESOURCE_CLASSLOADER_PATH_PREFIX 
  41.                 + getLocale().toString(); 
  42.         resourceBundle = ResourceBundle.getBundle( 
  43.                 RESOURCE_CLASSLOADER_PATH_FULL, getLocale()); 
  44.     } 
至此,剩下的工作就是刷新當前系統中的所有控件了!
我想我們當然不希望每個空間都實現一個監聽,各自進行刷新。這樣雖然可以實現功能,但是開發效率是比較低下的,而且也不易維護。所以,我考慮用一個“控件池”ComponentPool去統一管理當前系統中的所有控件。
 
  1. /** 
  2.  * UI控件池,用於保存當前所有的UI控件 
  3.  *  
  4.  * @author lihzh 
  5.  * @date 2012-3-18 下午10:21:14 
  6.  */ 
  7. public class ComponentPool { 
  8.  
  9.     // 按鈕池 
  10.     private static final Map<String, GenericButton> buttonPool = new HashMap<String, GenericButton>(); 
  11.  
  12.     /** 
  13.      * 註冊按鈕 
  14.      *  
  15.      * @param button 
  16.      * @author lihzh 
  17.      * @date 2012-3-18 下午10:26:35 
  18.      */ 
  19.     public static void addButton(GenericButton button) { 
  20.         if (button == null) { 
  21.             throw new IllegalArgumentException( 
  22.                     "The registing button can not be null."); 
  23.         } 
  24.         if (button.getName() == null) { 
  25.             throw new IllegalArgumentException( 
  26.                     "The registing button's name can not be null."); 
  27.         } 
  28.         buttonPool.put(button.getName(), button); 
  29.     } 
  30.  
  31.     /** 
  32.      * 獲取按鈕 
  33.      *  
  34.      * @param buttonName 
  35.      * @return 
  36.      * @author lihzh 
  37.      * @date 2012-3-18 下午10:30:31 
  38.      */ 
  39.     public static GenericButton getButton(String buttonName) { 
  40.         return buttonPool.get(buttonName); 
  41.     } 
  42.  
  43.     /** 
  44.      * 獲取所有的Button 
  45.      *  
  46.      * @return 
  47.      * @author lihzh 
  48.      * @date 2012-3-18 下午10:52:50 
  49.      */ 
  50.     public static Map<String, GenericButton> getAllButtons() { 
  51.         return buttonPool; 
  52.     } 
  53.  
我們以Button爲例。首先定義一個Button類型控件的基類GenericButton,所有具體的Button都需繼承自該基類。
 
  1. /** 
  2.  * 所有按鈕的基類,可統一處理國際化通用事件, 統一將name設置爲國際化文件中的key值 
  3.  *  
  4.  * @author lihzh 
  5.  * @date 2012-3-18 下午5:15:01 
  6.  */ 
  7. public class GenericButton extends JButton { 
  8.  
  9.     private static final long serialVersionUID = -5971509297727392232L; 
  10.  
  11.     public GenericButton(String name) { 
  12.         super(); 
  13.         this.setName(name); 
  14.         ComponentPool.addButton(this); 
  15.     } 
  16.      
  17.     /* 
  18.      * 設置名字,同時設置顯示值 
  19.      * @see java.awt.Component#setName(java.lang.String) 
  20.      * 
  21.      * @author lihzh 
  22.      * @date 2012-3-19 下午12:34:28 
  23.      */ 
  24.     @Override 
  25.     public void setName(String name) { 
  26.         super.setName(name); 
  27.         setText(ResourceBundleLoader.getValue(name)); 
  28.     } 
  29.      
  30.     /** 
  31.      * 在國際化位置發生改變時執行 
  32.      *  
  33.      * @author lihzh 
  34.      * @date 2012-3-18 下午10:35:54 
  35.      */ 
  36.     public void onLocaleChange() { 
  37.         this.setText(ResourceBundleLoader.getValue(this.getName())); 
  38.         afterTextChange(); 
  39.     } 
  40.  
  41.     /** 
  42.      * 在國際化位置發生改變時執行,用於子類複寫其特有操作。在文本刷新前執行 
  43.      *  
  44.      * @author lihzh 
  45.      * @date 2012-3-18 下午10:57:06 
  46.      */ 
  47.     protected void beforeTextChange() {      
  48.     } 
  49.      
  50.     /** 
  51.      * 在國際化位置發生改變時執行,用於子類複寫其特有操作。在文本刷新後執行 
  52.      *  
  53.      * @author lihzh 
  54.      * @date 2012-3-18 下午10:57:06 
  55.      */ 
  56.     protected void afterTextChange() {       
  57.     } 
該基類的構造函數中統一處理了Button顯示的文字信息,同時將當前Button註冊到了Pool中。
我們同樣的用I18UIHandler類去實現了Observer接口,同時觀察Locale變化的事件(注意與ResourceBundleLoader的先後順序)。
 
  1. /** 
  2.  * UI界面國際化管理類 
  3.  *  
  4.  * @author lihzh 
  5.  * @date 2012-3-18 下午10:09:29 
  6.  */ 
  7. public class I18nUIHandler implements Observer { 
  8.  
  9.     /* 
  10.      * @see java.util.Observer#update(java.util.Observable, java.lang.Object) 
  11.      * 
  12.      * @author lihzh 
  13.      * @date 2012-3-19 下午12:33:27 
  14.      */ 
  15.     @Override 
  16.     public void update(Observable o, Object arg) { 
  17.         // 刷新按鈕 
  18.         refreshButton(); 
  19.  
  20.     } 
  21.  
  22.     /** 
  23.      * 刷新按鈕 
  24.      *  
  25.      * @author lihzh 
  26.      * @date 2012-3-18 下午10:53:02 
  27.      */ 
  28.     private void refreshButton() { 
  29.         Map<String, GenericButton> buttons = ComponentPool.getAllButtons(); 
  30.         Iterator<Entry<String, GenericButton>> butEntryIt = buttons.entrySet().iterator(); 
  31.         while (butEntryIt.hasNext()) { 
  32.             Entry<String, GenericButton> entry = butEntryIt.next(); 
  33.             GenericButton button = entry.getValue(); 
  34.             button.onLocaleChange(); 
  35.         } 
  36.     } 
  37.  
在接口到變化後,從池中分類獲取所有控件。例如現在只有按鈕空間,則從池中獲取所有的按鈕,然後循環調用基類中onLocaleChange()方法,即可實現控件文字的動態刷新。如圖:

 系統中其他控件均可按此方式,實現國際化的動態切換。這樣一個簡單的Swing國際化框架的雛形就完成了。

具體控件代碼舉例:
 
  1. /** 
  2.  * 中文按鈕 
  3.  *  
  4.  * @author lihzh 
  5.  * @date 2012-3-18 下午11:00:46 
  6.  */ 
  7. public class CNConfirmButton extends GenericButton { 
  8.  
  9.     private static final long serialVersionUID = 1481761153745275594L; 
  10.      
  11.     public CNConfirmButton(String name) { 
  12.         super(name); 
  13.         setBounds(100808030); 
  14.         addActionListener(new ActionListener() { 
  15.             @Override 
  16.             public void actionPerformed(ActionEvent e) { 
  17.                 LocaleHandler.setLocale(Locale.CHINA); 
  18.             } 
  19.         }); 
  20.     } 
  21.      
容器代碼舉例:
 
  1. /** 
  2.  * @author lihzh 
  3.  * @date 2012-3-15 下午10:57:16 
  4.  */ 
  5. public class MainContainer extends JFrame { 
  6.  
  7.     private static final long serialVersionUID = -4379994966214808467L; 
  8.     private static final Logger logger = LoggerFactory 
  9.             .getLogger(MainContainer.class); 
  10.  
  11.     public MainContainer() { 
  12.  
  13.         logger.info("Begin to initialize the UI container."); 
  14.         this.setSize(300200); 
  15.         this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
  16.         this.setResizable(false); 
  17.         this.setTitle(getValue(TITLE_MAIN)); 
  18.         this.getContentPane().setLayout(null); 
  19.         JButton usButton = new USConfirmButton(COMBO_LANGUAGE_US); 
  20.         JButton cnButton = new CNConfirmButton(COMBO_LANGUAGE_ZH); 
  21.         this.add(usButton, null); 
  22.         this.add(cnButton, null); 
  23.     } 
  24.  
注:
1、這裏的容器並未實現國際化的動態刷新。
2、代碼import部分均爲加上,需自行引用。有些靜態方法和常量未加類名因爲採用的是靜態引用。
3、Log採用的是slf4j+logback
PS:所有源碼已上傳至GitHub:
git://github.com/lihongzheshuai/ForFun.git

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章