在 MFC 程序中,本地化是通过资源文件来完成的。菜单栏、对话框、字符串,图片等等资源都支持多个语言的副本。
在框架内部,是通过 CreateDialog、DialogBox、LoadMenu、LoadString、FindResource 来查找资源的。比如查找字符串:
inline const ATLSTRINGRESOURCEIMAGE* AtlGetStringResourceImage(
_In_ HINSTANCE hInstance,
_In_ UINT id) throw()
{
HRSRC hResource;
/*
The and operation (& static_cast
than WORD - this would cause a runtime error when the application is compiled with /RTCc flag.
*/
hResource = ::FindResourceW(hInstance, MAKEINTRESOURCEW( (((id>>4)+1) & static_cast
if( hResource == NULL )
{
return( NULL );
}
return _AtlGetStringResourceImage( hInstance, hResource, id );
}
这些函数有个特点:跟根据当前线程语言环境来定位资源。
当一个线程创建时,它使用用户语言环境。该值由 GetUserDefaultLCID 返回。
也就是说,在默认情况下资源加载是根据用户的区域设置来的决定。
使用 SetThreadLocale 可以改变当前线程的区域设置。从而可以改变 MFC 加载资源的语言。
::SetThreadLocale(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)); // 将语言环境改为 英语(美国)
CString title;
title.LoadString(AFX_IDS_APP_TITLE); // 会尝试加载 英语(美国) 字符资源
但是缺点很明显,这会影响所有涉及到字符串操作的地方,比如在中文系统上,线程语言改为英文,为用户提供一个通用对话框:
::SetThreadLocale(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));
CFileDialog dlg...
...
如果此时用户选了一个带有中文字符的路径,对话框将会得到一些乱码路径,问题还不仅仅只是这里而已。
所以不要使用 SetThreadLocale 来修改语言环境,除非你清楚自己在做什么。
正确做法应该是自行通过 FindResourceExA 来查找资源,这个函数不受线程语言影响,可以指定语言,但是缺点很明显,无法再使用 MFC 自带的一些加载资源的方法了。
从 Vista 开始,提出了一个线程UI语言的概念,这解决了上面提到的SetThreadLocale
影响全局的问题。通过 SetThreadUILanguage 或 SetThreadPreferredUILanguages 来改变当前线程的UI语言,而它仅仅只影响资源加载时的语言版本,不会影响线程的语言设置
LCID loc = ::GetThreadLocale(); // 2052
::SetThreadUILanguage(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));
loc = ::GetThreadLocale(); // 还是 2052
但是坑的是这个函数在XP下等同于SetThreadLocale
LCID loc = ::GetThreadLocale(); // 2052
::SetThreadUILanguage(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));
loc = ::GetThreadLocale(); // 1033
因为 XP 系统没有将线程语言和UI语言分离,所以不可以使用SetThreadLocale
或SetThreadUILanguage
去解决UI多语言问题。
有其他两个方案选择:
第一种方法属于正规手段,但是很繁琐,工程会比较多。
第二种方法理论上可行,但没有实践,不再展开讨论。
用户界面语言管理
SetThreadLocale function
SetThreadUILanguage function
创建纯资源 DLL
MFC 组件的本地化
MFC基于对话框使用dll进行多语言切换