CreateFont 的 charset 参数究竟设置了啥?
Posted
技术标签:
【中文标题】CreateFont 的 charset 参数究竟设置了啥?【英文标题】:What does the charset parameter of CreateFont exactly set?CreateFont 的 charset 参数究竟设置了什么? 【发布时间】:2011-09-17 06:28:07 【问题描述】:我的 Windows 上的代码页设置为 ANSI(Latin1,Windows-1252)。 我使用 CreateFont 创建字体并在 fdwCharSet 中传递 RUSSIAN_CHARSET
这是我的经历:
使用此字体的 Windows 控件(例如 Static)忽略字体的字符集:传递给 SetWindowTextA 的字符串以拉丁字符显示 在 DC 上选择此字体后,GDI 文本函数 (Ext)TextOutA 和 DrawTextA 使用该字体的字符集。传递给它们的字符串以西里尔字母显示。为什么?什么时候考虑字体的字符集参数,什么时候忽略它?我可以强制 Windows 控件使用字体的字符集吗?
【问题讨论】:
是的,使用 SetWindowTextW()。当代码页为 1252 时,SetWindowsTextA() 无法将俄语文本正确转换为 ansi 字符串。它没有任何西里尔字符。 我知道使用宽字符是正确的做法,但在我的情况下很难做到(非常大且相当旧的系统)。我想知道为什么字体字符集设置对于 GDI 文本函数和 Windows 本身绘制的控件的工作方式不同。 看来我不应该使用任何 xxxA api。 Unicode 是个好东西。 【参考方案1】:您必须将文本转换为 Unicode 并调用 SetWindowTextW()
而不是 SetWindowTextA()
。
确保窗口的类注册到RegisterClassW()
而不是RegisterClassA()
。这才是真正决定一个窗口是否是 Unicode 的。您可以使用IsWindowUnicode()
来验证窗口是否真的是Unicode。
确保将未处理的消息传递给 DefWindowProcW()
而不是 DefWindowProcA()
。
或者,如果窗口是一个对话框,只需确保它是使用CreateDialogW()
或DialogBoxParamW()
创建的。
【讨论】:
【参考方案2】:>我可以强制 Windows 控件使用字体的字符集吗?
AFAIK 不,你不能。
SetWindowTextA 只是将参数转换为 Unicode,然后调用 SetWindowTextW:Windows 内核、shell 和 GDI 都是 unicode。
要将参数转换为 Unicode,SetWindowTextA 使用 Window 的区域选项(“非 Unicode 程序的语言”)中的设置。
【讨论】:
没那么简单。仅当窗口是在 Unicode 模式而不是 ANSI 模式下创建时,SetWindowTextA 才会转换为 Unicode。对于 ANSI 窗口,到 Unicode 的转换似乎发生在 WM_PAINT,此时信息丢失。 我也承认在那里看到了这样的调用顺序:ExtTextout -> GetCodePage -> GDIGetEntry -> NtGdiGetCharSet -> win32u.NtGdiPolyPatBlt【参考方案3】:这是正在发生的事情:
-
您使用
"\xC4\xEE\xE1\xF0\xEE\xE5 xF3\xF2\xF0\xEE"
之类的名称调用 SetWindowText。
您被编译为 ANSI 应用程序而不是 Unicode,因此映射到对 SetWindowTextA 的调用。
SetWindowTextA 看到窗口是在 ANSI 模式下创建的,所以它直接设置字符串。 (如果它是一个 Unicode 窗口,那么它会将 ANSI 输入字符串转换为 Unicode 并将其传递给 SetWindowTextW。)
ANSI 窗口将 ANSI 字符串转换为 Unicode,以便可以显示它。但它不知道该字符串与系统的默认代码页位于不同的代码页中。正是这种转换将一切都改回了拉丁字符。它假定输入字符串在进程的默认代码页中(在您的情况下为 Windows 1252)。所以现在你有了一堆带重音的拉丁字符,而不是一串西里尔字母。
控件尝试使用 DrawTextW 或 TextOutW 之类的方式显示此 Unicode 字符串。
系统的下层部分说,“哦,废话,这个字符串是一堆拉丁字符,但是用户选择了西里尔字体。”为了解决这个问题,它使用字体链接(或字体回退,我把这些术语弄糊涂了)来有效地选择与 1252 兼容的字体。
您会看到拉丁语 gobbledygook 而不是正确的俄语。
我尝试想出一种最简单的方法来满足您的需求,但失败了。我的第一个想法是自己进行转换并直接调用 SetWindowTextW:
void SetWindowTextRussian(HWND hwnd, char *pszCyrillic)
const int cchCyrillic = ::lstrlen(pszCyrillic);
const int cchUnicode = 4 * cchCyrillic; // worst case
WCHAR *pszUnicode = new WCHAR[cchUnicode];
// See: http://msdn.microsoft.com/en-us/library/dd317756(v=vs.85).aspx
const UINT CP_CYRILLIC = 1251;
if (::MultiByteToWideChar(CP_CYRILLIC, 0, pszCyrillic, -1,
pszUnicode, cchUnicode) > 0)
::SetWindowTextW(hwnd, pszUnicode);
delete [] pszUnicode;
但这不起作用。我怀疑由于窗口是作为 ANSI 窗口创建的,Unicode 字符串被转换回 ANSI(再次假设错误的代码页),然后你得到问号而不是拉丁废话。
我认为您将不得不转换为 Unicode 应用程序,或者仅在默认代码页设置为 1251 的情况下运行。
更新:如果您控制窗口的创建(例如,您直接调用 CreateWindow 而不是让对话框实例化控件),那么您可以通过直接调用 CreateWindowW 来完成上述工作并为重要的控件创建一个 Unicode 窗口。
【讨论】:
感谢您的回答。我尝试了 CreateWindowW 解决方案,但没有帮助。 SetWindowTextA 中的 unicode 转换可能与 WM_PAINT 中的转换一样:它使用当前系统范围的代码页。但是,您的 SetWindowTextRussian 对我来说工作正常。解决方案将是类似的 - 不过,我看不出 DrawTextA 和 SetWindowTextA 的不同行为背后的原因。 UPDATE:好的,我现在看到您建议我同时使用 CreateWindowW 和 SetWindowTextRussian。实际上,这确实可以正常工作。 @Gabor Herman:很高兴你能成功。抱歉,如果我不够清楚,我建议同时使用 CreateWindowW 和 SetWindowTextRussian。【参考方案4】:考虑挂钩 gdi32full.dll GetCodePage 以选择您需要的代码页。例如 CP_UTF8。它具有单指针参数,返回单个 DWORD(代码页)和 stdcall 调用约定。
【讨论】:
以上是关于CreateFont 的 charset 参数究竟设置了啥?的主要内容,如果未能解决你的问题,请参考以下文章