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 参数究竟设置了啥?的主要内容,如果未能解决你的问题,请参考以下文章

使用CFont类来改变CStatic的字体

删除CreateFont创建的字体

Windows CreateFont:创立本人的字体

如何使用C语言改变字体大小,好像有一个setfront()的函数!但是好像不能用!

CreateFont函数为什么改变不了字体?该怎么解决

com.itextpdf.text.pdf.BaseFont.createFont