• 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发送状态改变通知。
  • 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信息变化时,会收到通知,改变需要加载的国际化文件。
  • * @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 去统一管理当前系统中的所有控件。
  • // 按钮池
  • 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都需继承自该基类。
  • 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的先后顺序)。
  • 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()方法,即可实现控件文字的动态刷新。如图:

    系统中其他控件均可按此方式,实现国际化的动态切换。这样一个简单的Swing国际化框架的雏形就完成了。

    具体控件代码举例:
  • 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);
  • 容器代码举例:
  • 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
    Java实现图书管理系统
    本篇文章是对目前Java专栏已有内容的一个总结练习,希望各位小主们在学习完面向对象的知识后,可以阅览本篇文章后,自己也动手实现一个这样的demo来加深总结应用已经学到知识并进行巩固。
    Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
    如果要在某一个界面里面添加功能的话,都在一个类中,会显得代码难以阅读,而且修改起来也会很困难,所以我们将游戏主界面、登录界面、以及注册界面都单独编成一个类,每一个类都继承JFrame父类,并且在类中创建方法来来实现页面
    Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
    注意由于我们计步功能的步数要在重写方法中用到,所以不能将初始化语句写在方法体内,而是要写在成员位置。在其名字的时候也要做到“见名知意”,所以我们给它起名字为step
    Java实现拼图小游戏(7)—— 作弊码和判断胜利
    当我们好不容易把拼图复原了,但是一点提示也没有,完全看不出来是成功了,那么我们就需要有判断胜利的功能去弹出“成功”类的图片,以便于玩家选择是重新开始还是退出小游戏
    Java实现拼图小游戏(6)—— 移动图片(键盘监听实操练习)
    当我们实现向上移动图片的时候,其实就是把空图片的下面一张图片往上移动,然后将空图片的下面那张图片设置为空图片,最后再调整初始位置为现在空图片所在位置即可,注意做完这些以后还要再加载图片,否则显示不出来
    Java实现拼图小游戏(5)—— 美化界面(含源码阅读)
    先加载的图片在上方,后加载的图片在下方,所以我们要把创建背景图的代码放在我们之前创建十五个小图片的代码后面,否则会出现背景图片将我们要拼的图片覆盖的情况