【问题标题】: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 是个好东西。

标签: winapi internationalization fonts character-encoding gdi


【解决方案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】:

    这是正在发生的事情:

    1. 您使用 "\xC4\xEE\xE1\xF0\xEE\xE5 xF3\xF2\xF0\xEE" 之类的名称调用 SetWindowText。
    2. 您被编译为 ANSI 应用程序而不是 Unicode,因此映射到对 SetWindowTextA 的调用。
    3. SetWindowTextA 看到窗口是在 ANSI 模式下创建的,所以它直接设置字符串。 (如果它是一个 Unicode 窗口,那么它会将 ANSI 输入字符串转换为 Unicode 并将其传递给 SetWindowTextW。)
    4. ANSI 窗口将 ANSI 字符串转换为 Unicode,以便可以显示它。但它不知道该字符串与系统的默认代码页位于不同的代码页中。正是这种转换将一切都改回了拉丁字符。它假定输入字符串在进程的默认代码页中(在您的情况下为 Windows 1252)。所以现在你有了一堆带重音的拉丁字符,而不是一串西里尔字母。
    5. 控件尝试使用 DrawTextW 或 TextOutW 之类的方式显示此 Unicode 字符串。
    6. 系统的下层部分说,“哦,废话,这个字符串是一堆拉丁字符,但是用户选择了西里尔字体。”为了解决这个问题,它使用字体链接(或字体回退,我把这些术语弄糊涂了)来有效地选择与 1252 兼容的字体。
    7. 您会看到拉丁语 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 调用约定。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-26
      • 2020-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-23
      • 2016-09-10
      • 2023-03-15
      相关资源
      最近更新 更多