您的位置:首页 > 其它

Windows环境下多语言版本软件的开发

2009-07-06 17:10 281 查看

一 背景介绍

在2005年,我曾经写过一篇文章《多语言版本软件产品的开发》,介绍了使用资源DLL的方法进行多语言软件的开发,主要做法是把软件中的资源独立出来,生成DLL文件,以当前语言的DLL文件为基础,生成不同语言的DLL文件,软件运行时,在初始化阶段根据用户的设置调用相应的语言的DLL文件,并把该DLL文件设置为当前资源文件。这种方法对于单独一个EXE的软件来讲,是一个不错的办法,但对于包括DLL的软件,尤其是DLL中涉及到多线程的情况,会存在模块资源共享和多线程资源共享的问题。另外,有人也提到一种方法,通过资源复制来实现不同语言版本,在编译时确定要生成的语言类型。上述两种方法都存在一个问题,就是对于同一资源,比如一个对话框,程序中会有多份(每种语言一份),当需要对对话框进行修改时,比如增加控件,或调整控件的位置,都需要对多个对话框进行调整,不利于软件的升级扩展。
 
通过前一段时间的实验,结合参考其它一些公司的做法,在这里,我们提出一种通过配置文件来实现多语言版本软件的开发方法。

二 方法概述

方法的基本思想是,把软件中窗口控件的Caption,菜单名称,资源字符串及代码中使用到字符串都在配置文件中进行保存,配置文件使用XML文件方式,软件运行时,根据用户设置的语言类型选择加载相应的配置文件。
 
下面详细描述不同类型数据的处理方法。

2.1 窗口控件Caption的处理方法

在配置文件中保存控件的ID和Caption的对应关系,如:
<item type="id">
        <item_id>1</item_id>
        <caption>确定</caption>
</item>
描述了控件ID为1,Caption为“确定”。在程序运行时,可以读取ID和Caption的值,通过ID和父窗口对话框的句柄得到控件的句柄,然后通过SetWindowText函数设置该控件要显示的Caption。
 
需要注意的是,如果在窗口设计过程中,放置到窗口上的控件没有更改它的ID,默认是IDC_STATIC,它的值是-1,在一个窗口中,可能会存在多个ID为IDC_STATIC的控件,这时候有两种方法可以进行处理,一是通过显示更改窗口的ID,指定一个唯一的ID,当然,对于已经完成的软件,这样做的话,会增加工作量,但如果在软件界面设计期间,我们还是推荐使用这种方法;另外一种方法是直接通过Caption来进行标识,我们通过如下保存方法:
 
 
<item type="desc">
  <item_id>联系电话:010-58851111</item_id>
  <caption>联系电话:010-58851111</caption>
</item>
在这种情况下,因为没有保存控件的ID,无法得到控件的句柄,只能通过循环窗口上所有控件的方法,把控件的Caption和配置文件中的item_id进行比较,然后设置控件新的Caption。
 
当需要多语言版本时,比如英文版,我们可以修改配置文件的上述信息如下:
<item type="id">
   <item_id>1</item_id>
   <caption>OK</caption>
</item>
 
<item type="desc">
 <item_id>联系电话:010-58851111</item_id>
  <caption>Call:010-58851111</caption>
</item>
 
当软件运行时,可以根据用户设置的语言类型,控制窗口上的所有控件的显示。
 

2.2 菜单的处理

对于软件中的菜单,我们可以采用类以控件的方法,保存菜单项的ID和Caption的对应关系:
<Item type="id">
  <item_id>57600</item_id>
  <caption>新建(&N) Ctrl+N</caption>
</Item>
我们知道,对于有子菜单的菜单项,比如像“文件”或“编辑”,它们的ID也是-1,这时,我们也需要像处理ID为-1的控件那样进行处理:
 
<Item type="desc">
  <item_id>文件(&F)</item_id>
  <caption>文件(&F)</caption>
</Item>
程序运行时,对于菜单Caption的更改,调用ModifyMenu函数,对于ID为-1的菜单也需要我们通过循环比较Caption内容的方式进行设置。
 
英文版的菜单配置如下所示:
<Item type="desc">
<item_id>文件(&F)</item_id>
  <caption>File(&F)</caption>
</Item>
 
<Item type="id">
  <item_id>57600</item_id>
  <caption>New(&N) Ctrl+N</caption>
</Item>
 

2.3 资源字符串的处理

资源中的字符每一项都有一个唯一的ID,我们可以在配置文件中保存ID和字符串内容的对应关系,如下所示:
<item>
  <item_id>57603</item_id>
  <item_value>保存活动文档保存</item_value>
</item>
在程序中调用LoadString函数的地方,替换为调用我们自己定义的函数,从配置文件中读取即可。

2.4 代码中使用字符串的处理

我们知道,在代码中有时也需要字符串,比如进行提示的地方,可能会有:
       char strValue[256]= {0} ;
sprintf(strValue, “欢迎使用公司产品”);
AfxMesageBox(strValue);
对于这样的字符串,我们有两种处理方法,一是把这些字符串写到资源字符中,把代码改为:
char strValue[256]= {0} ;
LoadString(IDS_VELCOME, strValue);
AfxMesageBox(strValue);
这样可以像“资源字符串的处理”一节中描述的那样,把LoadString替换为我们自己定义的读取配置文件的函数。
 
另一种方法是,把字符串直接保存到配置文件中,如:
<IDS_PROMPT>欢迎使用公司产品</IDS_PROMPT>
在原来的代码处,直接使用配置文件读取函数进行读取即可。在这里我们推荐使用前一种方法。

三 配置文件的生成

通过上面的介绍,我们了解了多语言版本软件,只需要在运行时,根据用户的设置读取相应的配置文件,更改窗口控件,菜单和字符串即可。但对于最初配置文件中的控件信息,菜单信息和字符串,如果一项一项手工添加是一件工作量不小的事情,而且手工编写容易出错。在这里,我们给出一种自动生成最初配置文件的方法。
注意:自动生成的过程代码,不是程序的运行代码,只需要在最初调用一次即可,或者当窗口控件发生调整后调用。

3.1 自动生成控件Caption的方法

对于窗口控件的Caption,我们首先需要知道窗口的句柄,通过遍历窗口子控件的方法进行保存,示例代码如下所示:
       /* 枚举hDlg窗口的所有子控件    */
      HWND hChild= ::GetWindow(hDlg, GW_CHILD);
      while (NULL != hChild)
      {
           GetWindowText(hChild, szValue, 1023);
           strCaption.Format("%s", szValue);
           int nID = GetDlgCtrlID(hChild);         
           if (-1 == nID)
           {
                 //保存Caption值
           }
           else
           {
                 //保存ID和Caption的对应关系
           }
           hChild =   GetNextWindow(hChild, GW_HWNDNEXT); 
      }
通过上述代码,实现自动窗口控件Caption的保存。

3.2 自动生成菜单信息的方法

对于菜单,我们首先需要知道菜单的句柄,然后遍历菜单的所有子菜单,示例代码如下:
void SaveMenuCaption(HMENU menu)  
{  
      int count = GetMenuItemCount(menu);
      char szCaption[1024] = {0};
      CString strID;
      for (int i = 0; i < count; i++)
      {
           int nLen = GetMenuString(menu, i, szCaption, 1023, MF_BYPOSITION);
           if (nLen > 0)
           {            &n, bsp; 
                 int nItemID = GetMenuItemID(menu, i);
                 xml.AddElem("Item");
                 if ( -1 == nItemID )
                 {
                      //保存菜单的Caption
                 }
                 else
                 {
                      //保存ID和Caption的对应关系
                 }
            }
           //是否有子菜单
           HMENU   hChild  = GetSubMenu(menu, i);  
           if (NULL != hChild )
           {
                 SaveMenuCaption(hChild);//递归,  
           }
      }
}
因为菜单可能存在多层,在这里,使用了递归调用的方法。
 

3.3 自动生成资源字符串的方法

对于字符串资源,目前我们还没有找到进行遍历的方法,但我们知道,资源中字符串的标识是从0到65535,这样我样可以通过循环这其间的所有的数,如果存在该字符串资源就进行保存,代码示例如下:
              #define MAX_STRING 65535
       char  szBuffer [4096] ;
      //循环所有资源
      for (int i = 0; i < MAX_STRING; i ++)
      {
           memset(szBuffer, 0, 4096);
           ::LoadString(hInstance, i, szBuffer, 4096);
           if (strlen(szBuffer) == 0)
           {
                 /* 下一个 */
                 continue;
           }
           /* 保存ID和字符串值的对应关系到XML文件中 */
          
      }
 
这种方法尽管效率低一些,但考虑到这不是运行代码,只有在设计配置文件时调用一次,也是可以接受的。同时如果对代码中的字符串使用推荐的保存成资源字符串的方法,可以一举两得把代码中的字符串也进行了保存。
 
完成上面的自动生成配置文件过程后,如果我们的开发环境是中文,我们将得到一份中文版本的配置文件,之后我们可以根据需要把这份中文版本的配置文件手工翻译为需要的多语言版本,在程序运行时指定要加载的配置文件即可。

四 注意事项

处理资源中的字符串时,有一点需要考虑,如果使用了MFC的文档视图框架,对于鼠标在工具栏移动时的ToolTip提示和状态栏提示,需要进行特殊的处理,我们知道ToolTip提示和状态栏提示时调用资源字符串的代码,封装在了MFC类中,没有显式地提供给我们接口,这时候需要我们通过重载CFrameWnd的GetMessageString函数和重写TTN_NEEDTEXT消息函数来进行处理,具体的过程就不再赘述。
 
示例
在实验过程中,我们通过两个配置文件,一个用于中文版本,一个用于英文版本,来实现两种版本的软件。
 
 
中文版本界面图
 
 
英文版本界面图

六 总结

通过配置文件来生成多语言版本,解决了之前的资源共享问题和窗口内容升级问题,并通过自动生成的方法,大大减轻了工作量,提高多语言版本生成的工作效率。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐