近10天,一直在忙活着让一个mfc中文版程序,支持设置其他语言。我用的stringtable来实现的,这个方法比较简单高效,就是实现起来枯燥了一些。昨天晚上刚忙完,今天总结一下实现过程中关键的几个点。
1、stringtable。
stringtable在此处主要是用来做翻译的,比如,对话框的caption,控件的windowtext,MessageBox的一些提示,我们都可以在各个语言对应的stringtable中,添加其翻译过来的内容。
(1)先添加stringtable。
若当前项目没有stringtable,就选择项目后,右键,选择“添加”----“资源”----“stringtable”----“新建”。
若当前项目有stringtable,就切换到资源视图,选择一个stringtable,右键,选择“插入副本”,然后选择想要添加哪种语言的stringtable即可。插入的新副本的stringtable。就是之前stringtable的复制版,然后翻译里面的内容。
(2)在stringtable中添加或者修改内容。
在中文stringtable中添加如下内容:
在英文stringtable中添加如下内容
若stringtable是从别的语言的stringtable复制过来的,就直接翻译,修改内容即可。
2、在代码中设置控件语言。
在控件所属对话框的类成员函数OnInitDialog()中,设置所属该对话框的控件的内容,比如“确认”“取消”按钮,以及我在对话框中间添加的静态文本框,,它们的控件ID分别为IDOK,IDCANCEL,ID_STATIC,则代码如下:
CString strLoad;
stLoad.LoadString(BTN_OK);
GetDlgItem(IDOK)->SetWindowText(strLoad);//设置“确认”按钮显示的内容
stLoad.LoadString(BTN_CANCLE);
GetDlgItem(IDCANCEL)->SetWindowText(strLoad);//设置“取消”按钮显示的内容
strLoad.LoadString(STATIC_CONTENT);
GetDlgItem(ID_STATIC)->SetWindowText(strLoad);//设置静态文本框显示的内容
3、设置线程和UI的语言。
在theApp类对象所属的类成员函数initInstance()中,设置线程和UI语言。函数如下:
LANGID idLang = SetThreadUILanguage(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT));//设置线程UI语言为英语
SetThreadLocale(idLang);//设置线程语言和UI语言一致
LANGID idLang = SetThreadUILanguage(MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), SORT_DEFAULT));//设置线程UI语言为中文
SetThreadUILocale(idLang);//设置线程语言和UI语言一致
设置了线程和UI语言后,在第二步的CString.LoadString(ID)时,程序就会根据设置的语言,读取对应语言的stringtable中对应字符串ID的内容。
至此,设置语言的基本功能已经实现了。效果如下:
设置语言为英文时:
设置语言为中文时:
至此,实现mfc程序多语言设置的工作基本完成,但在具体实现中,还遇到下面几个问题。
1、设置MessageBox按钮的语言。
刚开始做多语言设置时,网上查了很多资料,都说MessageBox中的确认取消按钮的语言,是根据系统语言来变化的,自己设置不了。后来我师父说MessageBoxEx可以实现。比如,我在关闭对话框时,弹出“是否确认关闭对话框”的消息提示,实现如下:
(1)在中英两个stringtable中添加提醒内容:
中文stringtable:
英文stringtable
(2)在对话框的OnClose()消息中添加消息处理:
CString strLoad;
strLoad.LoadString(MSG_BOX_CLOSE);
int iRet = MessageBoxEx(NULL, strLoad, L"Warn", MB_OKCANCEL | MB_ICONWARNING, GetThreadLocale());
if(iRet == IDCANCEL)
{
return;
}
设置为中文时:
设置为英文时:
可以看到,只要我们设置了MessageBoxEx函数的最后一个参数,就可以切换MessageBox中的确认取消按钮的语言,但MessageBox的提示内容和标题,还得自己在stringtable中添加,然后在代码中设置。
2、菜单。
菜单实现多语言,跟stringtable类似,在资源里,选择该菜单,右键,“插入副本”,然后选择要添加的语言,添加后,直接在菜单上翻译就行。我们在代码里设置了线程和UI语言后,程序会根据设置的语言,自动选择相应语言的菜单。
如果想在代码里动态的修改菜单语言,代码如下:
CMenu*pMainMenu = CMenu::FromHandle(m_wndMenuBar.GetDefaultMenu());
if(pMainMenu != nullptr)
{
CString strLoad;
//修改一级菜单,比如文件,工具,视图,设置等
strLoad.LoadString(MENU_FILE);
pMainMenu->ModifyMenu(0, MF_BYPOSITION | MF_STRING, 0, strLoad);//修改一级菜单的第一项
strLoad.LoadString(MENU_EDIT);
pMainMenu->ModifyMenu(1, MF_BYPOSITION | MF_STRING, 0, strLoad);//修改一级菜单的第二项
//以此类推,修改一级菜单的第几项,就将ModifyMenu的第一个参数设置为从0开始的索引值,只要不用响应消息,第三个参数就为0;需要响应消息的话,就必须传入菜单对应的控件ID
//...
//修改一级菜单第一项的子菜单(也就是二级菜单),比如文件菜单下的新建文件,打开文件,保存文件,等等
CMenu*pSubMenu = pMainMenu->GetSubMenu(0);
if(pSubMenu != nullptr)
{
//注意,此时的菜单是需要响应具体消息的,所以ModifyMenu的第三个参数,必须是实际的菜单按钮的ID值,因为消息映射里,消息影响函数是和控件ID绑定的
strLoad.LoadString(MENU_OPEN_NEW);
pSubMenu->ModifyMenu(0, MF_BYPOSITION | MF_STRING, IDC_MRU_FILE_NEW, strLoad);
strLoad.LoadString(MENU_OPEN_FILE);
pSubMenu->ModifyMenu(1, MF_BYPOSITION | MF_STRING, IDC_MRU_FILE_OPEN, strLoad);
}
}
//下面这行代码很重要,只要修改了主菜单,必须在修改后加上这行,不然菜单更新不及时
m_wndMenuBar.CreateFromMenu(pMainMenu->GetSafeHmenu(), TRUE, TRUE);
pMainMenu.Detach();
需要注意的是,菜单里的横线也是占据一个索引项的,比如:
注意索引为3和5的横线。
3、多线程的问题。
前面我们也看到了,MFC设置语言,是以线程为单位来设置的,所以需要在每个线程里,都通过调用SetThreadUILanguage和SetThreadLocale设置语言。如果发现某些地方的语言设置没起作用,就先检查是否在该线程中设置了语言。
4、资源访问问题。
如果引用了dll模块,在dll中需要使用dll自带的stringtable资源,那么在LoadString时最好指定dll的资源句柄。
获取dll的资源句柄有两种情况:如果该dll有dllMain函数,该函数的第一个参数就是该dll的资源句柄,将该句柄传给LoadString的第一个参数即可。如果没有dllMain函数,就在该dll的app的initInstance函数中添加如下代码:
HINSTANCE g_instance;
BOOL testDllApp::InitInstance()
{
CWinApp::InitInstance();
AFX_MODULE_STATE* state = AfxGetModuleState();
g_instance = state->m_hCurrentInstanceHandle;
return TRUE;
}
然后将g_instance传给LoadString的第一个参数即可。
以上就是我实现mfc程序设置多语言功能的总结。上述代码都是一字一字在网页里编辑的,可能会有手误,望见谅。