вторник, 24 мая 2022 г.

Как Windows определяет, должно ли вновь созданное окно использовать ориентацию LTR или RTL?

Это перевод How does Windows decide whether a newly-created window should use LTR or RTL layout? Автор: Реймонд Чен.

В Extended стилях окна можно указать, будет ли оно отображаться слева направо (Left-To-Right - LTR) или справа налево (Right To Left - RTL). Раскладка справа налево используется в языках с написанием справа налево - из которых сегодня наиболее широко используются, вероятно, арабский и иврит. Вы можете попросить Windows сделать окно с ориентацией RTL, установив Extended-стиль WS_EX_LAYOUTRTL.

Если вы не укажете Extended-стиль WS_EX_LAYOUTRTL, система всё равно может применить этот стиль автоматически на основе следующих правил:

Сценарий Правило
Дочернее окно
(child window)
Родитель не указывает WS_EX_NO­INHERIT­LAYOUT Наследует WS_EX_LAYOUTRTL от родителя.
Родитель указывает WS_EX_NO­INHERIT­LAYOUT Остаётся LTR.
Окно верхнего уровня
(top-level window)
Есть владелец (owned) Остаётся LTR.
Нет владельца Согласно Get­Process­Default­Layout.

В случае окна верхнего уровня без владельца Extended-стиль WS_EX_LAYOUTRTL устанавливается автоматически, если в настройках по умолчанию процесса установлен бит LAYOUT_RTL.

Как я заметил некоторое время назад, если процесс ни разу не вызывает функцию Set­Process­Default­Layout, то начальные настройки процесса определяются путём проверки свойства версии File­Description основного исполняемого файла: если оно начинается с двух меток LRM (Left-To-Right Mark, это кодовая точка Unicode U+200E), то в настройки процесса добавляется LAYOUT_RTL.

Ранее я также отметил, что вы можете запросить у Get­Locale­Info­Ex свойство LOCALE_IREADING­LAYOUT, чтобы определить, является ли какой-либо конкретный язык LTR или RTL.
// Направление:
// 0 = слева направо (например, английский, русский и т.п.)
// 1 = справаналево (например, арабский)
// 2 = сверху вниз, справа налево (например, классический китайский)
// 3 = свурху вниз, слева направо (например, монгольский)

function GetLanguageReadingLayout(const ALanguageName: String): Integer;
begin
  Win32Check(Boolean(
    GetLocaleInfoEx(ALanguageName,
                    LOCALE_IREADINGLAYOUT or LOCALE_RETURN_NUMBER,
                    PWideChar(@Result),
                    SizeOf(Result) div SizeOf(Char))));
end;

function GetSystemDefaultLanguageReadingLayout: Integer;
begin
  Result := GetLanguageReadingLayout(LOCALE_NAME_SYSTEM_DEFAULT);
end;

function GetUserDefaultLanguageReadingLayout: Integer;
begin
  Result := GetLanguageReadingLayout(LOCALE_NAME_USER_DEFAULT);
end;
Но обычно вас интересует основной язык для текущего потока, так как именно он больше всего влияет на языковые ресурсы, используемые вашей программой. Вы можете использовать Get­Thread­Preferred­UI­Languages, чтобы получить все языки, применимые к текущему потоку, а затем передать первый язык в нашу вспомогательную функцию Get­Language­Reading­Layout. Вызов функции Get­Thread­Preferred­UI­Languages немного разочаровывает, потому что список применимых языков может меняться асинхронно (если другой поток вызывает Set­Process­Preferred­UI­Languages). Это вдвойне раздражает, потому что вспомогательная функция Adapt­Fixed­Size­To­Allocated­Result работает с нуль-терминированными строками и не поддерживает строки, заканчивающиеся двойным нулём, поэтому придётся писать цикл вручную:
// Возвращает строку вида
// 'ru-RU'#0'ru'#0'en-US'#0'en'#0#0
function GetThreadPreferredUILanguages(const AFlags: DWORD): String;
var
  Count, BufferSize: Integer;
  Error: Cardinal;
begin
  Count := 0;
  BufferSize := 0;
  SetLastError(0);
  Win32Check(WinAPI.Windows.GetThreadPreferredUILanguages(AFlags, @Count, nil, @BufferSize));
  repeat
    SetLength(Result, BufferSize);
    if WinAPI.Windows.GetThreadPreferredUILanguages(AFlags, @Count, PChar(Result), @BufferSize) then
      Error := 0
    else
      Error := GetLastError;
  until Error <> ERROR_INSUFFICIENT_BUFFER;
  Win32Check(Error = 0);
end;
Теперь мы можем подключить это к нашей существующей функции:
function GetDefaultThreadLanguageReadingLayout: Integer;
begin
  Result := GetLanguageReadingLayout(PChar(GetThreadPreferredUILanguages(MUI_LANGUAGE_NAME or MUI_UI_FALLBACK)));
end;
Извините, что получилось так сложно.

Бонусная болтовня: вся эта логика предполагает, что ваша программа изначально была переведена на язык RTL. Если ваша программа предназначена только для английского/русского языка, не отображайте английские/русские строки в RTL. Как я отметил в этой статье, вы можете добавлять метки в строковые ресурсы, чтобы указать в каком направлении ресурсы ожидают чтения строк.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.