之前说到要想看看Swing。不知道为啥我第一个想到的问题是界面语言显示的国际化问题。管他呢,既然随心所遇学,想到了就实现来看看。
想要国际化,首先还是要有国际化资源文件。
我们所要做的就是在切换语言的时候,从对应的资源文件中读取值信息,显示出来。
要实时响应语言的变化,可以采用JDK自带的观察者方式来实现。
这里封装了一个ObserverCenter,用于统一发送被观察实例的变化状态信息。
- /**
- * 观察者中心,用于统一通知发送变更信
- *
- * @author lihzh
- * @date 2012-3-17 下午9:54:15
- */
- public class ObserveCenter extends Observable {
- private static ObserveCenter observeCenter;
- private ObserveCenter(){}
- public static ObserveCenter getInstance() {
- if (observeCenter == null) {
- observeCenter = new ObserveCenter();
- }
- return observeCenter;
- }
- /**
- * 发送变更消息, 相当于{@link #notifyChange(null)}
- *
- * @author lihzh
- * @date 2012-3-17 下午9:58:19
- */
- public void notifyChange() {
- notifyChange(null);
- }
- /**
- * 发送变更信息
- *
- * @param obj
- * @author lihzh
- * @date 2012-3-17 下午10:01:50
- */
- public void notifyChange(Object obj) {
- setChanged();
- notifyObservers(obj);
- }
- }
LocaleHandler类用于设置区域信息,同时在设置区域信息后,发送通过ObserverCenter发送状态改变通知。
- /**
- * 当前位置(国际化信息所用)管理类
- *
- * @author lihzh
- * @date 2012-3-17 下午4:26:43
- */
- public class LocaleHandler {
- private static final Logger logger = LoggerFactory.getLogger(LocaleHandler.class);
- public static final Locale DEFAULT_LOCAL = Locale.CHINA;
- private static Locale locale = DEFAULT_LOCAL;
- static {
- ObserveCenter.getInstance().addObserver(new I18nUIHandler());
- ObserveCenter.getInstance().addObserver(new ResourceBundleLoader());
- }
- /**
- * 获取国际化区域信息
- *
- * @return
- * @author lihzh
- * @date 2012-3-17 下午9:32:13
- */
- public static Locale getLocale() {
- return locale;
- }
- /**
- * 设置国际化区域信息
- *
- * @param locale
- * @author lihzh
- * @date 2012-3-17 下午9:31:47
- */
- public static void setLocale(Locale locale) {
- LocaleHandler.locale = locale == null ? DEFAULT_LOCAL : locale;
- logger.info("Current locale is: [{}]", LocaleHandler.locale.toString());
- refresh();
- }
- /**
- * 设置国际化区域信息,相当于{@link #setLocale(DEFAULT_LOCAL)}
- *
- * @author lihzh
- * @date 2012-3-17 下午9:35:00
- */
- public static void setLocale() {
- setLocale(DEFAULT_LOCAL);
- }
- /**
- * 刷新国际化信息
- *
- * @author lihzh
- * @date 2012-3-17 下午9:31:58
- */
- private static void refresh() {
- ObserveCenter.getInstance().notifyChange();
- }
- }
ResourceBundleLoader
类,用于根据区域设置加载对应的国际化配置文件。该类实现了Observer接口。在Locale信息变化时,会收到通知,改变需要加载的国际化文件。
- /**
- * 国际化文件读取类,提供静态方法和静态引用
- *
- * @author lihzh
- * @date 2012-3-17 下午4:07:24
- */
- public class ResourceBundleLoader implements Observer {
- private static final String RESOURCE_CLASSLOADER_PATH_PREFIX = "i18n.i18n_";
- private static String RESOURCE_CLASSLOADER_PATH_FULL;
- private static ResourceBundle resourceBundle;
- /**
- * 读取国际化文件中定义的值
- *
- * @param key
- * @return
- * @author lihzh
- * @date 2012-3-17 下午4:33:32
- */
- public static String getValue(String key) {
- return resourceBundle.getString(key);
- }
- /*
- * 当Locale环境变量改变时更改resourceBundle实例加载的国际化文件
- */
- @Override
- public void update(Observable o, Object arg) {
- refreshBundle();
- }
- /**
- * 刷新读取的Bundle信息
- *
- * @author lihzh
- * @date 2012-3-17 下午10:13:26
- */
- private static void refreshBundle() {
- RESOURCE_CLASSLOADER_PATH_FULL = RESOURCE_CLASSLOADER_PATH_PREFIX
- + getLocale().toString();
- resourceBundle = ResourceBundle.getBundle(
- RESOURCE_CLASSLOADER_PATH_FULL, getLocale());
- }
- }
至此,剩下的工作就是刷新当前系统中的所有控件了!
我想我们当然不希望每个空间都实现一个监听,各自进行刷新。这样虽然可以实现功能,但是开发效率是比较低下的,而且也不易维护。所以,我考虑用一个“控件池”ComponentPool去统一管理当前系统中的所有控件。
- /**
- * UI控件池,用于保存当前所有的UI控件
- *
- * @author lihzh
- * @date 2012-3-18 下午10:21:14
- */
- public class ComponentPool {
- // 按钮池
- private static final Map<String, GenericButton> buttonPool = new HashMap<String, GenericButton>();
- /**
- * 注册按钮
- *
- * @param button
- * @author lihzh
- * @date 2012-3-18 下午10:26:35
- */
- public static void addButton(GenericButton button) {
- if (button == null) {
- throw new IllegalArgumentException(
- "The registing button can not be null.");
- }
- if (button.getName() == null) {
- throw new IllegalArgumentException(
- "The registing button's name can not be null.");
- }
- buttonPool.put(button.getName(), button);
- }
- /**
- * 获取按钮
- *
- * @param buttonName
- * @return
- * @author lihzh
- * @date 2012-3-18 下午10:30:31
- */
- public static GenericButton getButton(String buttonName) {
- return buttonPool.get(buttonName);
- }
- /**
- * 获取所有的Button
- *
- * @return
- * @author lihzh
- * @date 2012-3-18 下午10:52:50
- */
- public static Map<String, GenericButton> getAllButtons() {
- return buttonPool;
- }
- }
我们以Button为例。首先定义一个Button类型控件的基类GenericButton,所有具体的Button都需继承自该基类。
- /**
- * 所有按钮的基类,可统一处理国际化通用事件, 统一将name设置为国际化文件中的key值
- *
- * @author lihzh
- * @date 2012-3-18 下午5:15:01
- */
- public class GenericButton extends JButton {
- private static final long serialVersionUID = -5971509297727392232L;
- public GenericButton(String name) {
- super();
- this.setName(name);
- ComponentPool.addButton(this);
- }
- /*
- * 设置名字,同时设置显示值
- * @see java.awt.Component#setName(java.lang.String)
- *
- * @author lihzh
- * @date 2012-3-19 下午12:34:28
- */
- @Override
- public void setName(String name) {
- super.setName(name);
- setText(ResourceBundleLoader.getValue(name));
- }
- /**
- * 在国际化位置发生改变时执行
- *
- * @author lihzh
- * @date 2012-3-18 下午10:35:54
- */
- public void onLocaleChange() {
- this.setText(ResourceBundleLoader.getValue(this.getName()));
- afterTextChange();
- }
- /**
- * 在国际化位置发生改变时执行,用于子类复写其特有操作。在文本刷新前执行
- *
- * @author lihzh
- * @date 2012-3-18 下午10:57:06
- */
- protected void beforeTextChange() {
- }
- /**
- * 在国际化位置发生改变时执行,用于子类复写其特有操作。在文本刷新后执行
- *
- * @author lihzh
- * @date 2012-3-18 下午10:57:06
- */
- protected void afterTextChange() {
- }
该基类的构造函数中统一处理了Button显示的文字信息,同时将当前Button注册到了Pool中。
我们同样的用I18UIHandler类去实现了Observer接口,同时观察Locale变化的事件(注意与ResourceBundleLoader的先后顺序)。
- /**
- * UI界面国际化管理类
- *
- * @author lihzh
- * @date 2012-3-18 下午10:09:29
- */
- public class I18nUIHandler implements Observer {
- /*
- * @see java.util.Observer#update(java.util.Observable, java.lang.Object)
- *
- * @author lihzh
- * @date 2012-3-19 下午12:33:27
- */
- @Override
- public void update(Observable o, Object arg) {
- // 刷新按钮
- refreshButton();
- }
- /**
- * 刷新按钮
- *
- * @author lihzh
- * @date 2012-3-18 下午10:53:02
- */
- private void refreshButton() {
- Map<String, GenericButton> buttons = ComponentPool.getAllButtons();
- Iterator<Entry<String, GenericButton>> butEntryIt = buttons.entrySet().iterator();
- while (butEntryIt.hasNext()) {
- Entry<String, GenericButton> entry = butEntryIt.next();
- GenericButton button = entry.getValue();
- button.onLocaleChange();
- }
- }
- }
在接口到变化后,从池中分类获取所有控件。例如现在只有按钮空间,则从池中获取所有的按钮,然后循环调用基类中onLocaleChange()方法,即可实现控件文字的动态刷新。如图:
具体控件代码举例:
- /**
- * 中文按钮
- *
- * @author lihzh
- * @date 2012-3-18 下午11:00:46
- */
- public class CNConfirmButton extends GenericButton {
- private static final long serialVersionUID = 1481761153745275594L;
- public CNConfirmButton(String name) {
- super(name);
- setBounds(100, 80, 80, 30);
- addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- LocaleHandler.setLocale(Locale.CHINA);
- }
- });
- }
- }
容器代码举例:
- /**
- * @author lihzh
- * @date 2012-3-15 下午10:57:16
- */
- public class MainContainer extends JFrame {
- private static final long serialVersionUID = -4379994966214808467L;
- private static final Logger logger = LoggerFactory
- .getLogger(MainContainer.class);
- public MainContainer() {
- logger.info("Begin to initialize the UI container.");
- this.setSize(300, 200);
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- this.setResizable(false);
- this.setTitle(getValue(TITLE_MAIN));
- this.getContentPane().setLayout(null);
- JButton usButton = new USConfirmButton(COMBO_LANGUAGE_US);
- JButton cnButton = new CNConfirmButton(COMBO_LANGUAGE_ZH);
- this.add(usButton, null);
- this.add(cnButton, null);
- }
- }
注:
1、这里的容器并未实现国际化的动态刷新。
2、代码import部分均为加上,需自行引用。有些静态方法和常量未加类名因为采用的是静态引用。
3、Log采用的是slf4j+logback
PS:所有源码已上传至GitHub:
git://github.com/lihongzheshuai/ForFun.git