Практика
Модернизация TouchKeyboard

:: Меню ::
:: На главную ::
:: FAQ ::
:: Заметки ::
:: Практика ::
:: Win API ::
:: Проекты ::
:: Скачать ::
:: Секреты ::
:: Ссылки ::

:: Сервис ::
:: Написать ::

:: MVP ::

:: RSS ::

Яндекс.Метрика


TouchKeyboard - компонент виртуальной клавиатуры, имитирующий традиционную клавиатуру компьютера. В своем комплекте она имеет несколько макетов - цифровую клавиатуру и несколько клавиатур со стандартной раскладкой для целого ряда языков ввода (от 101 клавиши в американской раскладке до 106 клавиш в японской). Достаточно удобное решение для программ, которые планируется эксплуатировать на планшете.

В процессе работы с этой клавиатурой обнаружился весьма существенный недостаток - она не позволяет переключаться между клавиатурными раскладками, сколько не долби по клавишам Ctrl + Shift (или Alt + Shift, в зависимости от настройки вашей системы). Такой бедой не страдает ни экранная клавиатура (osk.exe), в которой эти сочетания клавиш прекрасно отрабатывают, ни сенсорная (tabtip.exe), в которой для переключения между языками предусмотрена специальная кнопка.


Надо ли говорить о том, что отсутствие возможности переключения языков способно доставить пользователям весьма ощутимые неудобства. Попробуем решить эту проблему, и для начала разберемся, что представляет собой TouchKeyboard.

Макеты (раскладки клавиатуры) - это обычные ресурсы. Чтобы подключить их к своему приложению, достаточно добавить в uses модуль Vcl.Touch.Keyboard.

uses
  Vcl.Touch.Keyboard;

Посмотрим, какие макеты мы имеем в своем распоряжении, для чего напишем небольшую вспомогательную утилиту (ее полный исходный код находится в каталоге HelperTool в архиве с примерами к данной статье).

type
  PEnumData = ^TEnumData;
  TEnumData = record
    Lines: TStrings;
  end;

function EnumResNames(Module: HMODULE; ResType, ResName: PChar;
  LParam: NativeInt): BOOL; stdcall;
const
  sResourceName = 'KEYBOARD';
begin
   if Pos(sResourceName, ResName) > 0 then
      PEnumData(LParam)^.Lines.Add(ResName);
   Result := True;
end;

procedure TKeyboardTranslator.GetKeyboardLayoutNames;
var
  EnumData: PEnumData;
begin
   try
      New(EnumData);
      EnumData^.Lines := FLayoutNames;
      try
         EnumResourceNames(HInstance, RT_RCDATA, @EnumResNames, LParam(EnumData));
      except
         FLayoutNames.Clear;
      end;
   finally
      Dispose(EnumData);
   end;
end;

После запуска утилиты мы должны увидеть такой список:


Чтобы было проще разбираться с тем, что представляет собой конкретный макет, сериализуем его в формат XML. Для этого выберем требуемый макет из списка и нажмем на кнопку "Выгрузить в XML" (при этом выгружаемый файл сформируется в одном каталоге с утилитой).

procedure TKeyboardTranslator.SaveToXml(const FileName, LayoutName: string);

  function LoadLayout(const LayoutName: string): TVirtualKeyLayout;
  begin
     {...}
  end;

  function ComboKeysToString(const Keys: TKeyDataArray): string;
  begin
     {...}
  end;

  procedure MakeXmlDocument(var Document: IXMLDocument; KeyboardLayout: TVirtualKeyLayout);
  begin
     {...}
  end;

var
  Document: IXMLDocument;
  Layout: TVirtualKeyLayout;
begin
   if (Trim(FileName) <> '') and (Trim(LayoutName) <> '') then
   begin
      Layout := LoadLayout(LayoutName);

      if Assigned(Layout) then
      begin
         Document := TXMLDocument.Create(nil);
         if Document = nil then
            Exit;

         Document.NodeIndentStr := #9;
         Document.Options:= Document.Options + [doNodeAutoIndent];
         Document.Active := True;

         MakeXmlDocument(Document, Layout);

         Document.SaveToFile(FileName);
      end;
   end;
end;

Здесь мы загружаем из ресурса нужный макет (представляющий собой ни что иное как экземпляр класса TVirtualKeyLayout), и пробегаясь по его свойствам формируем XML файл. Код вспомогательных методов я намеренно не привожу из-за его достаточно больших размеров, это помешает нам сконцентрироваться на сути.

Вот результат сериализации ресурса STANDARD101KEYBOARD из Delphi 10.2.2 (в других версиях результат может, и скорее всего будет, немного отличаться):

<Keyboard KeyboardName="Standard101" KeyboardType="Standard" Width="848" Height="262" MinWidth="550"
	MinHeight="180" RowHeight="48">
	<Language LanguageName="az" Name="Azeri"/>
	<Language LanguageName="be" Name="Belarusian"/>
	<Language LanguageName="en" Name="English"/>
	<Language LanguageName="es" Name="Spanish"/>
	<Language LanguageName="hy" Name="Armenian"/>
	<Language LanguageName="ka" Name="Georgian"/>
	<Language LanguageName="nl-BE" Name="Dutch (Belgium)"/>
	<Language LanguageName="ru" Name="Russian"/>
	<Language LanguageName="sr" Name="Serbian"/>
	<Language LanguageName="lt" Name="Lithuainian"/>
	<Row TopMargin="0" BottomMargin="2">
		<Key Caption="Esc" ScanCode="1" Width="64" Height="48" RightMargin="2" Stretch="True"
			PublishedName="Esc"/>
		<Key ScanCode="41" Width="48" Height="48" LeftMargin="2" RightMargin="2"/>
		<Key ScanCode="2" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="59" ModifierName="fn" FontSize="10" Caption="F1"/>
			<Modifier ScanCode="2" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key ScanCode="3" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="60" ModifierName="fn" FontSize="10" Caption="F2"/>
			<Modifier ScanCode="3" ModifierName="caps" LanguageName="hy"/>
			<Modifier ScanCode="3" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key ScanCode="4" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="61" ModifierName="fn" FontSize="10" Caption="F3"/>
			<Modifier ScanCode="4" ModifierName="caps" LanguageName="hy"/>
			<Modifier ScanCode="4" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key ScanCode="5" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="62" ModifierName="fn" FontSize="10" Caption="F4"/>
			<Modifier ScanCode="5" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key ScanCode="6" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="63" ModifierName="fn" FontSize="10" Caption="F5"/>
			<Modifier ScanCode="6" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key ScanCode="7" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="64" ModifierName="fn" FontSize="10" Caption="F6"/>
			<Modifier ScanCode="7" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key ScanCode="8" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="65" ModifierName="fn" FontSize="10" Caption="F7"/>
			<Modifier ScanCode="8" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key ScanCode="9" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="66" ModifierName="fn" FontSize="10" Caption="F8"/>
			<Modifier ScanCode="9" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key ScanCode="10" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="67" ModifierName="fn" FontSize="10" Caption="F9"/>
		</Key>
		<Key ScanCode="11" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="68" ModifierName="fn" FontSize="10" Caption="F10"/>
			<Modifier ScanCode="11" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key ScanCode="12" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="69" ModifierName="fn" FontSize="10" Caption="F11"/>
			<Modifier ScanCode="12" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key ScanCode="13" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="70" ModifierName="fn" FontSize="10" Caption="F12"/>
			<Modifier ScanCode="13" ModifierName="caps" LanguageName="hy"/>
			<Modifier ScanCode="13" ModifierName="caps" LanguageName="lt"/>
		</Key>
		<Key KeyImage="3" ScanCode="14" Width="78" Height="48" LeftMargin="2" Stretch="True"
			PublishedName="Backspace"/>
	</Row>
	<Row TopMargin="2" BottomMargin="2">
		<Key KeyImage="0" ScanCode="15" Width="85" Height="48" RightMargin="2" Stretch="True"
			PublishedName="Tab"/>
		<Key ScanCode="16" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="16" ModifierName="caps"/>
		</Key>
		<Key ScanCode="17" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="17" ModifierName="caps"/>
		</Key>
		<Key ScanCode="18" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="18" ModifierName="caps"/>
		</Key>
		<Key ScanCode="19" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="19" ModifierName="caps"/>
		</Key>
		<Key ScanCode="20" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="20" ModifierName="caps"/>
		</Key>
		<Key ScanCode="21" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="21" ModifierName="caps"/>
		</Key>
		<Key ScanCode="22" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="22" ModifierName="caps"/>
		</Key>
		<Key ScanCode="23" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="23" ModifierName="caps"/>
		</Key>
		<Key ScanCode="24" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="24" ModifierName="caps"/>
		</Key>
		<Key ScanCode="25" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="25" ModifierName="caps"/>
		</Key>
		<Key ScanCode="26" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="26" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key ScanCode="27" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="27" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key ScanCode="43" Width="48" Height="48" LeftMargin="2" RightMargin="2"/>
		<Key Caption="Del" ScanCode="83" Width="57" Height="48" LeftMargin="2" Stretch="True"
			PublishedName="Del">
			<Language LanguageName="es" Caption="Supr"/>
		</Key>
	</Row>
	<Row TopMargin="2" BottomMargin="2">
		<Key Caption="Caps" ScanCode="58" Width="115" Height="48" RightMargin="2" Toggle="True"
			Stretch="True" ModifierName="caps" PublishedName="Caps">
			<Language LanguageName="es" Caption="Bloq May"/>
		</Key>
		<Key ScanCode="30" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="30" ModifierName="caps"/>
		</Key>
		<Key ScanCode="31" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="31" ModifierName="caps"/>
		</Key>
		<Key ScanCode="32" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="32" ModifierName="caps"/>
		</Key>
		<Key ScanCode="33" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="33" ModifierName="caps"/>
		</Key>
		<Key ScanCode="34" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="34" ModifierName="caps"/>
		</Key>
		<Key ScanCode="35" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="35" ModifierName="caps"/>
		</Key>
		<Key ScanCode="36" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="36" ModifierName="caps"/>
		</Key>
		<Key ScanCode="37" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="37" ModifierName="caps"/>
		</Key>
		<Key ScanCode="38" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="38" ModifierName="caps"/>
		</Key>
		<Key ScanCode="39" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="39" ModifierName="caps" LanguageName="es"/>
			<Modifier ScanCode="39" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key ScanCode="40" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="40" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key KeyImage="2" ScanCode="28" Width="123" Height="48" LeftMargin="2" Stretch="True"
			PublishedName="Enter"/>
	</Row>
	<Row TopMargin="2" BottomMargin="2">
		<Key KeyImage="1" ScanCode="42" Width="125" Height="48" RightMargin="2" Stretch="True"
			ModifierName="shift" PublishedName="LeftShift"/>
		<Key ScanCode="44" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="44" ModifierName="caps"/>
		</Key>
		<Key ScanCode="45" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="45" ModifierName="caps"/>
		</Key>
		<Key ScanCode="46" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="46" ModifierName="caps"/>
		</Key>
		<Key ScanCode="47" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="47" ModifierName="caps"/>
		</Key>
		<Key ScanCode="48" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="48" ModifierName="caps"/>
		</Key>
		<Key ScanCode="49" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="49" ModifierName="caps"/>
		</Key>
		<Key ScanCode="50" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="50" ModifierName="caps"/>
		</Key>
		<Key ScanCode="51" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="51" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key ScanCode="52" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="52" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key ScanCode="53" Width="48" Height="48" LeftMargin="2" RightMargin="2">
			<Modifier ScanCode="53" ModifierName="caps" LanguageName="hy"/>
		</Key>
		<Key KeyImage="1" ScanCode="42" Width="65" Height="48" LeftMargin="2" RightMargin="2"
			Stretch="True" ModifierName="shift" PublishedName="RightShift"/>
		<Key KeyImage="4" ScanCode="72" Width="48" Height="48" LeftMargin="2" RightMargin="2"/>
		<Key Caption="Fn" ScanCode="-2" Width="48" Height="48" LeftMargin="2" Toggle="True"
			ModifierName="fn"/>
	</Row>
	<Row TopMargin="2" BottomMargin="0">
		<Key Caption="Ctrl" ScanCode="29" Width="68" Height="48" RightMargin="2" ModifierName="ctrl"
			PublishedName="LeftCtrl"/>
		<Key Caption="Alt" ScanCode="56" Width="82" Height="48" LeftMargin="2" RightMargin="2" ModifierName="alt"
			PublishedName="LeftAlt"/>
		<Key ScanCode="57" Width="321" Height="48" LeftMargin="2" RightMargin="2" Stretch="True"/>
		<Key Caption="Alt" ScanCode="56" Width="82" Height="48" LeftMargin="2" RightMargin="2" ModifierName="alt"
			PublishedName="RightAlt" ComboKeys="vk=17;sc=29;vk=18;sc=56">
			<Language LanguageName="ka" Caption="AltGr"/>
			<Language LanguageName="sr" Caption="AltGr"/>
			<Language LanguageName="es" Caption="AltGr"/>
		</Key>
		<Key Caption="Ctrl" ScanCode="29" Width="69" Height="48" LeftMargin="2" RightMargin="2" ModifierName="ctrl"
			PublishedName="RightCtrl"/>
		<Key KeyImage="6" ScanCode="75" Width="48" Height="48" LeftMargin="2" RightMargin="2"/>
		<Key KeyImage="5" ScanCode="80" Width="48" Height="48" LeftMargin="2" RightMargin="2"/>
		<Key KeyImage="7" ScanCode="77" Width="48" Height="48" LeftMargin="2"/>
	</Row>
</Keyboard>

Первое, что мы видим - языки, поддерживаемые данным макетом. Далее идут ряды клавиш в тегах <Row>..</Row>. Это наводит на мысль о том, что мы можем добавить в макет свою кнопку для переключения языков, аналогично сенсорной клавиатуре (tabtip.exe). Мне показалось удобным поместить эту клавишу в нижний ряд после правой клавиши Ctrl.

Данная кнопка должна отражать в заголовке раскладку для выбранного в данный момент языка в системе. К счастью в TouchKeyboard такой механизм уже предусмотрен, обратите внимание на строчки для правой кнопки Alt:

<Key Caption="Alt" ScanCode="56" Width="82" Height="48" LeftMargin="2" RightMargin="2" ModifierName="alt"
	PublishedName="RightAlt" ComboKeys="vk=17;sc=29;vk=18;sc=56">
	<Language LanguageName="ka" Caption="AltGr"/>
	<Language LanguageName="sr" Caption="AltGr"/>
	<Language LanguageName="es" Caption="AltGr"/>
</Key>

Осталось определить соответствие между тегом языка (например, ru-RU) и заголовком для кнопки к нему. Подробно останавливаться на этом здесь я не буду, кому интересно, прочтите статью "Управление языками и раскладками в Windows". Все найденные соответствия (возможно на вашей машине список будет отличаться от того, что получил я) вставляем в XML файл (проделаем это со всеми тремя макетами):

<Row TopMargin="2" BottomMargin="0">
	<Key Caption="Ctrl" ScanCode="29" Width="68" Height="48" RightMargin="2" ModifierName="ctrl"
	PublishedName="LeftCtrl"/>
	<Key Caption="Alt" ScanCode="56" Width="82" Height="48" LeftMargin="2" RightMargin="2" ModifierName="alt"
		PublishedName="LeftAlt"/>
	<Key ScanCode="57" Width="321" Height="48" LeftMargin="2" RightMargin="2" Stretch="True"/>
	<Key Caption="Alt" ScanCode="56" Width="82" Height="48" LeftMargin="2" RightMargin="2" ModifierName="alt"
		PublishedName="RightAlt" ComboKeys="vk=17;sc=29;vk=18;sc=56">
		<Language LanguageName="ka" Caption="AltGr"/>
		<Language LanguageName="sr" Caption="AltGr"/>
		<Language LanguageName="es" Caption="AltGr"/>
	</Key>
	<Key Caption="Ctrl" ScanCode="29" Width="69" Height="48" LeftMargin="2" RightMargin="2" ModifierName="ctrl"
		PublishedName="RightCtrl"/>
	<Key ScanCode="-255" ComboKeys="vk=-255;sc=-255" Width="65" Height="48" LeftMargin="2" RightMargin="2">
			<Language LanguageName="iu-Cans-CA" Caption="IU"/>
			<Language LanguageName="bs-Cyrl-BA" Caption="BS"/>
			<Language LanguageName="en-IE" Caption="EN"/>
			<Language LanguageName="zh-MO" Caption="ZH"/>
			<Language LanguageName="fr-CH" Caption="FR"/>
			<Language LanguageName="en-CA" Caption="EN"/>
			<Language LanguageName="zh-SG" Caption="ZH"/>
			<Language LanguageName="sr-Cyrl-CS" Caption="SR"/>
			<Language LanguageName="fr-CA" Caption="FR"/>
			<Language LanguageName="zh-HK" Caption="ZH"/>
			<Language LanguageName="iu-Latn-CA" Caption="IU"/>
			<Language LanguageName="mn-Mong-CN" Caption="MN"/>
			<Language LanguageName="uz-Cyrl-UZ" Caption="UZ"/>
			<Language LanguageName="se-SE" Caption="SM"/>
			<Language LanguageName="az-Cyrl-AZ" Caption="AZ"/>
			<Language LanguageName="sr-Latn-CS" Caption="SR"/>
			<Language LanguageName="pt-PT" Caption="PT"/>
			<Language LanguageName="nl-BE" Caption="NL"/>
			<Language LanguageName="fr-BE" Caption="FR"/>
			<Language LanguageName="es-MX" Caption="ES"/>
			<Language LanguageName="en-GB" Caption="EN"/>
			<Language LanguageName="de-CH" Caption="DE"/>
			<Language LanguageName="zh-CN" Caption="CH"/>
			<Language LanguageName="wo-SN" Caption="WO"/>
			<Language LanguageName="sah-RU" Caption="SA"/>
			<Language LanguageName="mi-NZ" Caption="MR"/>
			<Language LanguageName="ug-CN" Caption="UI"/>
			<Language LanguageName="ig-NG" Caption="IB"/>
			<Language LanguageName="kl-GL" Caption="KA"/>
			<Language LanguageName="lb-LU" Caption="LB"/>
			<Language LanguageName="ba-RU" Caption="BA"/>
			<Language LanguageName="nso-ZA" Caption="NS"/>
			<Language LanguageName="yo-NG" Caption="YO"/>
			<Language LanguageName="ha-Latn-NG" Caption="HA"/>
			<Language LanguageName="dv-MV" Caption="DI"/>
			<Language LanguageName="ps-AF" Caption="PA"/>
			<Language LanguageName="ne-NP" Caption="NE"/>
			<Language LanguageName="si-LK" Caption="SI"/>
			<Language LanguageName="syr-SY" Caption="SY"/>
			<Language LanguageName="lo-LA" Caption="LA"/>
			<Language LanguageName="km-KH" Caption="KH"/>
			<Language LanguageName="cy-GB" Caption="CY"/>
			<Language LanguageName="bo-CN" Caption="BO"/>
			<Language LanguageName="mn-MN" Caption="MN"/>
			<Language LanguageName="mr-IN" Caption="MA"/>
			<Language LanguageName="as-IN" Caption="AS"/>
			<Language LanguageName="ml-IN" Caption="MY"/>
			<Language LanguageName="kn-IN" Caption="KD"/>
			<Language LanguageName="te-IN" Caption="TE"/>
			<Language LanguageName="ta-IN" Caption="TA"/>
			<Language LanguageName="or-IN" Caption="OR"/>
			<Language LanguageName="gu-IN" Caption="GU"/>
			<Language LanguageName="pa-IN" Caption="PA"/>
			<Language LanguageName="bn-IN" Caption="BN"/>
			<Language LanguageName="tt-RU" Caption="TT"/>
			<Language LanguageName="tk-TM" Caption="TU"/>
			<Language LanguageName="ky-KG" Caption="KY"/>
			<Language LanguageName="kk-KZ" Caption="KK"/>
			<Language LanguageName="se-NO" Caption="SM"/>
			<Language LanguageName="mt-MT" Caption="ML"/>
			<Language LanguageName="hi-IN" Caption="HI"/>
			<Language LanguageName="fo-FO" Caption="FO"/>
			<Language LanguageName="ka-GE" Caption="KA"/>
			<Language LanguageName="tn-ZA" Caption="TS"/>
			<Language LanguageName="mk-MK" Caption="MK"/>
			<Language LanguageName="hsb-DE" Caption="HS"/>
			<Language LanguageName="az-Latn-AZ" Caption="AZ"/>
			<Language LanguageName="hy-AM" Caption="HY"/>
			<Language LanguageName="vi-VN" Caption="VI"/>
			<Language LanguageName="fa-IR" Caption="FA"/>
			<Language LanguageName="tg-Cyrl-TJ" Caption="TA"/>
			<Language LanguageName="lt-LT" Caption="LT"/>
			<Language LanguageName="lv-LV" Caption="LV"/>
			<Language LanguageName="et-EE" Caption="ET"/>
			<Language LanguageName="sl-SI" Caption="SL"/>
			<Language LanguageName="be-BY" Caption="BE"/>
			<Language LanguageName="uk-UA" Caption="UK"/>
			<Language LanguageName="ur-PK" Caption="UR"/>
			<Language LanguageName="tr-TR" Caption="TR"/>
			<Language LanguageName="th-TH" Caption="TH"/>
			<Language LanguageName="sv-SE" Caption="SV"/>
			<Language LanguageName="sq-AL" Caption="SQ"/>
			<Language LanguageName="sk-SK" Caption="SK"/>
			<Language LanguageName="hr-HR" Caption="HR"/>
			<Language LanguageName="ru-RU" Caption="RU"/>
			<Language LanguageName="ro-RO" Caption="RO"/>
			<Language LanguageName="pt-BR" Caption="PT"/>
			<Language LanguageName="pl-PL" Caption="PL"/>
			<Language LanguageName="nb-NO" Caption="NO"/>
			<Language LanguageName="nl-NL" Caption="NL"/>
			<Language LanguageName="ko-KR" Caption="KO"/>
			<Language LanguageName="ja-JP" Caption="JP"/>
			<Language LanguageName="it-IT" Caption="IT"/>
			<Language LanguageName="is-IS" Caption="IS"/>
			<Language LanguageName="hu-HU" Caption="HU"/>
			<Language LanguageName="he-IL" Caption="HE"/>
			<Language LanguageName="fr-FR" Caption="FR"/>
			<Language LanguageName="fi-FI" Caption="FI"/>
			<Language LanguageName="es-ES_tradnl" Caption="ES"/>
			<Language LanguageName="en-US" Caption="EN"/>
			<Language LanguageName="el-GR" Caption="EL"/>
			<Language LanguageName="de-DE" Caption="DE"/>
			<Language LanguageName="da-DK" Caption="DA"/>
			<Language LanguageName="cs-CZ" Caption="CS"/>
			<Language LanguageName="zh-TW" Caption="CH"/>
			<Language LanguageName="bg-BG" Caption="BG"/>
			<Language LanguageName="ar-SA" Caption="AR"/>
	</Key>
	<Key KeyImage="6" ScanCode="75" Width="48" Height="48" LeftMargin="2" RightMargin="2"/>
	<Key KeyImage="5" ScanCode="80" Width="48" Height="48" LeftMargin="2" RightMargin="2"/>
	<Key KeyImage="7" ScanCode="77" Width="48" Height="48" LeftMargin="2"/>
</Row>

Конечно, с точки зрения перфикционизма, было бы правильнее добавлять в каждый макет только те языки, которые реально им поддерживаются - это позволило бы сократить размер получившегося в итоге ресурса. Но, так как экономия в размере весьма не существенна по сравнению со временем, затраченным на анализ и выборку нужных языков, то в рамках данной статьи я не буду этого делать. (Примеры модифицированных ресурсов можно найти в каталоге ModifyedResource в архиве с примерами к данной статье).

Осталось десериализовать наш модифицированный XML файл (напомню, он должен лежать в одном каталоге с утилитой), для чего нажмем на кнопку "Скомпилировать", предварительно выбрав нужный ресурс в списке.

function TKeyboardTranslator.Compile(const FileName: string;
  const OutFileName: string = ''): Boolean;
var
  TmpFileName: string;
  KeyboardLayout: TVirtualKeyLayout;
  Stream: TFileStream;
  Temp: TVirtualKeyLayout;
begin
   Result := False;

   if FileExists(FileName) then
   begin
      KeyboardLayout := LoadXmlFile(FileName);

      if OutFileName = '' then
         TmpFileName := ChangeFileExt(FileName, '.res')
      else
      begin
         TmpFileName := OutFileName;
         if LowerCase(ExtractFileExt(TmpFileName)) <> '.res' then
            TmpFileName := TmpFileName + '.res';
      end;

      if FileExists(TmpFileName) then
         Stream := TFileStream.Create(TmpFileName, fmOpenWrite)
      else
         Stream := TFileStream.Create(TmpFileName, fmCreate);

      try
         KeyboardLayout.SaveToStream(Stream);
         Result := True;
      finally
         Stream.Free;
      end;
   end;
end;

Скомпилированный ресурс готов и находится рядом с XML файлом. Уже сейчас, в качестве эксперимента, можно с помощью какого-нибудь редактора ресурсов подменить в тестовом приложении модифицированные ресурсы. Запустив модифицированное приложение мы увидим нашу кнопку, которая корректно отображает выбранный в системе язык, и корректно ведет себя при переключении раскладки.

Нам же нужно подключить скомпилированный ресурс к своему проекту (предполагается, что проект уже содержит компонент TouchKeyboard). Для этого создадим файл keyboard.rc с таким содержимым:

STANDARD101KEYBOARD RCDATA "STANDARD101KEYBOARD.res"
STANDARD102KEYBOARD RCDATA "STANDARD102KEYBOARD.res"
STANDARD106KEYBOARD RCDATA "STANDARD106KEYBOARD.res"

Подключим этот файл к проекту через "Project" -> "Add to Project..." (горячие клавиши Shift + F11). При этом в файле проекта должна появиться строка:

{$R 'keyboard.res' 'keyboard.rc'}

Компилируем и наблюдаем заветную кнопку! Но это только половина дела, ведь ее еще нужно заставить работать.

type
  TTouchKeyboard = class(Vcl.Touch.Keyboard.TTouchKeyboard)
  private
    procedure WMTouch(var Message: TMessage); message WM_TOUCH;
    procedure ProcessKeyPress(const Msg: TWmMouse; ID: Integer);
    function TouchInputToWmMouse(Control: TWinControl; const TouchInput: TouchInput): TWMMouse;
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

implementation

procedure TTouchKeyboard.WMTouch(var Message: TMessage);
var
  TouchInputs: array of TouchInput;
  LTouchInput: TouchInput;
  TouchCount: Integer;
begin
   TouchCount := LoWord(Message.WParam);
   SetLength(TouchInputs, TouchCount);

   if GetTouchInputInfo(Message.LParam, TouchCount, @TouchInputs[0], SizeOf(TouchInput)) then
   begin
      if Length(TouchInputs) = 1 then
      begin
         LTouchInput := TouchInputs[0];
         ProcessKeyPress(TouchInputToWmMouse(Self, LTouchInput), LTouchInput.dwID);
      end;
   end;

   inherited;
end;

procedure TTouchKeyboard.WndProc(var Message: TMessage);
begin
   if not ((ssTouch in MouseOriginToShiftState) and CheckWin32Version(6, 1)) then
   begin
      if Message.Msg = WM_LBUTTONDBLCLK then
         Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
      ProcessKeyPress(TWMMouse(Message), 0);
   end;

   inherited;
end;

procedure TTouchKeyboard.ProcessKeyPress(const Msg: TWmMouse; ID: Integer);

  function FindButton(Point: TPoint): TCustomKeyboardButton; overload;
  var
    Index: Integer;
  begin
     Result := nil;

     for Index := 0 to ButtonsCount-1 do
        if Buttons[Index].BoundsRect.Contains(Point) then
        begin
           Result := Buttons[Index];
           Break;
        end;
  end;

const
  INPUTLANGCHANGE_FORWARD  = $0002;
  INPUTLANGCHANGE_BACKWARD = $0004;
var
  Button: TCustomKeyboardButton;
begin
   if not Enabled or (csDesigning in ComponentState) then
      Exit;

   case Msg.Msg of
      WM_LBUTTONUP:
      begin
         Button := FindButton(SmallPointToPoint(Msg.Pos));

         {.$DEFINE CHECK_VK}

         if Button <> nil then
            {$IFDEF CHECK_VK}
            if Length(Button.Key.ComboKeys) = 1 then
               if (Button.Key.ComboKeys[0].Vk = -255) and
                  (Button.Key.ComboKeys[0].ScanCode = -255) then
            {$ELSE}
            if Button.Key.ScanCode = -255 then
            {$ENDIF}
               if (GetAsyncKeyState(VK_SHIFT) shr 16) and 1 = 1 then
                  PostMessage(Application.Handle, WM_INPUTLANGCHANGEREQUEST,
                     INPUTLANGCHANGE_FORWARD, 0)
               else
                  PostMessage(Application.Handle, WM_INPUTLANGCHANGEREQUEST,
                     INPUTLANGCHANGE_BACKWARD, 1);
      end;
   end;
end;

function TTouchKeyboard.TouchInputToWmMouse(Control: TWinControl;
  const TouchInput: TouchInput): TWMMouse;
var
  P: TPoint;
begin
   Result.Msg := WM_NULL;
   if TouchInput.dwFlags and TOUCHEVENTF_DOWN <> 0 then
      Result.Msg := WM_LBUTTONDOWN
   else
   if TouchInput.dwFlags and TOUCHEVENTF_UP <> 0 then
      Result.Msg := WM_LBUTTONUP
   else
   if TouchInput.dwFlags and TOUCHEVENTF_MOVE <> 0 then
      Result.Msg := WM_MOUSEMOVE;

   P := Point(TouchInput.X div 100, TouchInput.Y div 100);
   PhysicalToLogicalPoint(Control.Handle, P);
   Result.Pos := PointToSmallPoint(Control.ScreenToClient(P));
end;

Как обычно я воспользуюсь приемом с подменой класса. Нужно предусмотреть различные варианты взаимодействия пользователя с клавиатурой - как с помощью мыши, так и при помощи пальца на планшетах с сенсорным экраном. Для удобства и единообразия все тачи (нажатия пальцем) преобразуются в события мыши, и обрабатываются в одном методе. Что именно обрабатывать, пару ScanCode и Vk, или только ScanCode, в данном случае не принципиально, в примере я показал оба варианта.

Теперь, умея модифицировать TouchKeyboard, можно добавлять на нее и другие кнопки, например Windows. Обработчик ее нажатия очень прост:

SendMessage(Self.Handle, WM_SYSCOMMAND, SC_TASKLIST, 0);

Сложность здесь будет заключаться в отрисовке логотипа на кнопке. Можно посмотреть, как эта задача решается в самом компоненте, и разобраться с процедурой TCustomKeyboardButton.Paint (и ее подпрограммами PaintTab, PaintEnter, PaintTallEnter, PaintBackspace, PaintShift и PaintArrow) в модуле Vcl.Touch.Keyboard. Впрочем, это уже совсем другая история.

Ну вот, пользоваться приложением с модифицированной клавиатурой стало гораздо удобнее, а значит цель достигнута. На этом все, заботьтесь о своих пользователях!

.: Пример к данной статье :.


При использовании материала - ссылка на сайт обязательна