Собственно суть вопроса в том, как текст, набранный правильно, но при неправильной раскладке клавиатуры перекодировать в нужную раскладку. По сути то же самое, что делает PuntoSwitcher, но только более универсально, для любых раскладок. То что "накопал" по этому вопросу постараюсь предельно просто изложить.
Каждая раскладка имеет собственное уникальное имя. Имя раскладки представляет из себя строку, состоящую из набора цифр. Например для английской раскладки (основной в Windows) это имя -
00000409, для русской -
00000419. С ними мы пока и будем проводить проверку.
Список всех установленных в системе раскладок можно найти в реестре
Code |
1
| HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts
|
|
Имена вложенных ключей совпадают с именами раскладок. По каждому можно получить дополнительную информацию из параметров, больше всего видимо интересен параметр Layout Text
Кроме того, из параметров ключа
Code |
1
| HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout\DosKeybCodes
|
|
можно получить код для создания экземпляра System.Globalization.CultureInfo, например для вывода на экран имени языка раскладки на этом самом языке, ну и не только разумеется.
Каждая клавиша на клавиатуре имеет свой уникальный код, причем она выдает именно этот код, независимо от раскладок и чего бы то ни было. Клавиатура вообще "не знает" ровным счетом ничего о раскладках или любых других настройках компьютера. Но получив код от клавиатуры, операционная система уже сама интерпретирует его в соответствии с активной раскладкой клавиатуры и таким образом в текстовое поле могут вводиться разные символы, реагируя на одну и ту же клавишу.
Воспользуемся этим обстоятельством и сначала полученный текст перекодируем в коды клавиш с помощью исходной (неправильной раскладки), после чего нам понадобится эти коды перекодировать обратно уже с помощью раскладки правильной.
Нам надо импортировать пространства имен
vb.net |
1
2
| Imports System.Runtime.InteropServices
Imports System.Text
|
|
Для получения кодов клавиш по символам и раскладке нам понадобится следующая функция
vb.net |
1
2
3
| <DllImport("user32.dll")> _
Private Function VkKeyScanEx(ch As Char, dwhkl As IntPtr) As Short
End Function
|
|
Она принимает символ и указатель раскладки. Но у нас раскладки даны не в указателях, а в именах, поэтому для получения указателя по имени нам нужна еще такая функция
vb.net |
1
2
3
| <DllImport("user32")>
Function LoadKeyboardLayout(pwszKLID As String, Flags As UInteger) As IntPtr
End Function
|
|
Первый ее параметр - это как раз имя раскладки, вторму можно передавать ноль.
Еще, для того, чтобы получить символ Юникода из кода клавиши и раскладки нам потребуется импортировать еще одну функцию из библиотеки user32
vb.net |
1
2
3
4
5
6
7
8
9
10
| Public Declare Function ToUnicodeEx Lib "user32" (
wVirtKey As UInteger,
wScanCode As UInteger,
lpKeyState As Byte(),
<Out()>
<MarshalAs(UnmanagedType.LPWStr, SizeConst:=64)>
ByVal lpChar As System.Text.StringBuilder,
cchBuff As Integer,
wFlags As UInteger,
dwhkl As IntPtr) As Integer
|
|
С импортом пока все. Теперь можно писать основной код. Главная функция, в которой будет происходить вся магия, будет принимать три аргумента: собственно строку для перекодирования, а так же исходную и целевую раскладки в виде указателей.
vb.net |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| Function ConvertStringByKeyboardLayout(srcStr As String, LayoutFrom As IntPtr, LayoutTo As IntPtr) As String
Dim result As New StringBuilder
For Each c As Char In srcStr
Dim keyScan = VkKeyScanEx(c, LayoutFrom)
If keyScan = -1 Then
result.Append(c)
Continue For
End If
Dim keydead = BitConverter.GetBytes(keyScan)(1)
Dim keyState(255) As Byte
If keydead And 1 Then
keyState(16) = 255
End If
If keydead And 2 Then
keyState(17) = 255
End If
If keydead And 4 Then
keyState(18) = 255
End If
Dim sbstring As New StringBuilder
ToUnicodeEx(keyScan, 0, keyState, sbstring, 5, 0, LayoutTo)
result.Append(sbstring.ToString)
Next
Return result.ToString
End Function
|
|
Поясню, что здесь происходит. Поскольку встроенные механизмы операционной системы обрабатывают сигналы с клавиатуры по одному, у нас есть инструменты для обработки отдельных символов, а не всей строки целиком. Таким образом, полученную строку мы обходим посимвольно в цикле, перекодируем каждый символ и добавляем результат в объект StringBuilder под названием result. Сначала нам надо получить код символа и мы для этой цели применяем VkKeyScanEx, передавая ей символ и исходную раскладку клавиатуры. Функция может возвратить либо код символа, либо -1. Последнее означает, что такого символа в данной раскладке нет. Мы проверяем keyScan на равенство -1 и далее можно поступить по-разному, я просто добавляю символ без обработки в выходную коллекцию и перехожу к следующей итерации. Надо сказать, что у такого подхода есть недостатки, поскольку неизвестно, был ли символ перекодирован, а кроме того, многие символы есть в разных раскладках(например знаки препинания) но расположены они в них на разных клавишах, а это может привести к некорректной замене символа. Можно сделать так, чтобы в этом случае выбрасывалось исключение или возвращалась пустая строка. Можно добавить в функцию еще один параметр, который будет определять, как функция должна вести себя в подобных случаях. Тут уже кому как удобнее.
Относительно переменной keyDead. Здесь опять-таки нужно небольшое пояснение. Код любой клавиши на клавиатуре - это один байт. Этого вполне достаточно, поскольку на любой клавиатуре клавиш чуть больше ста, а байт дает 256 комбинаций. Тем не менее скан клавиши имеет тип Short, а не байт и занимает такое число два байта вместо одного. При этом первый байт - это собственно код клавиши, а второй - это флаги клавиш-модификаторов. И в этом втором байте первый бит, установленный в 1 означает, что нажата клавиша Shift (в принципе нас в основном это и интересует, поскольку от этого зависит регистр символа). Второй - Ctrl. Третий - Alt. Четвертый - Hankaku(что-то нужное для ввода символов азиатских алфавитов). Остальные зарезервированы и из назначение зависит от конкретных драйверов.
Таким образом переменная keydead - это как раз байт, с интересующими нас модификаторами.
Байтовый массив keyState служит практически тем же целям, что и keydead только уже для перевода кода клавиши в в символ юникода в новой раскладке. В этом массиве 256 элементов, каждый соответствует конкретной клавише, по коду клавиши находится индекс элемента. Если клавиша зажата, то соответствующий ей байт будет равен 255, в противном случае - 0. Нам нужно указать состояние Shift, Ctrl и Alt, то есть инициировать элементы 16, 17 и 18 соответственно, но только если соответсвующие флажки установлены в keydead.
Далее создаем StringBuilder для получения результата и вызываем ToUnicodeEx, который передает результат в этот StringBuilder, а из него символ записывается в result.
Теперь у нас есть функция для перекодирования строк, но она не очень удобна, поскольку требует передавать указатели на раскладки. Немного упростим вызов, создав функцию, которая будет загружать раскладки по имени.
vb.net |
1
2
3
4
5
| Function ConvertStringByKeyboardLayoutName(srcStr As String, strLayoutFrom As String, strLayoutTo As String) As String
Dim layoutFrom = LoadKeyboardLayout(strLayoutFrom, 0)
Dim layoutTo = LoadKeyboardLayout(strLayoutTo, 0)
Return ConvertStringByKeyboardLayout(srcStr, layoutFrom, layoutTo)
End Function
|
|
Ну и теперь можно создать две готовые функции для перекодирования с русской раскладки на английскую и обратно. Зная имена раскладок это сделать совсем несложно.
vb.net |
1
2
3
4
5
6
7
| Function ConvertEnToRuByKBL(srcStr As String) As String
Return ConvertStringByKeyboardLayoutName(srcStr, "00000409", "00000419")
End Function
Function ConvertRuToEnByKBL(srcStr As String) As String
Return ConvertStringByKeyboardLayoutName(srcStr, "00000419", "00000409")
End Function
|
|
Вот таким кодом теперь можно протестировать перевод с английской на русскую раскладку
vb.net |
1
2
| Console.WriteLine(ConvertEnToRuByKBL(Console.ReadLine()))
Console.ReadKey()
|
|
В дополнение еще следует сказать о том, как можно получить имя активной раскладки клавиатуры.
vb.net |
1
2
3
4
5
6
7
8
9
10
11
12
13
| <DllImport("user32")>
Function GetKeyboardLayoutName(<Out> pwszKLID As StringBuilder) As Boolean
End Function
Function GetKeyboardLayoutName() As String
Dim sb As New StringBuilder
Dim result = GetKeyboardLayoutName(sb)
If result Then
Return sb.ToString
Else
Return ""
End If
End Function
|
|
Вторая функция - просто удобная оболочка для первой. Она возвращает имя текущей раскладки. Правда в консольном приложении она будет возвращать имя раскладки, в которой было запущено приложение, так что в консольном приложении ее лучше не использовать.
Можно так же сразу загрузить текущую раскладку с помощью
vb.net |
1
| Public Declare Function GetKeyboardLayout Lib "user32" (ByVal idThread As UInteger) As IntPtr
|
|
Она принимает ид потока, для активного потока надо передать 0. Но в консольном приложении результат будет тем же, что и для предыдущей функции.
Есть еще функция, возвращающая все активные раскладки системы.
vb.net |
1
2
3
| <DllImport("user32.dll")>
Function GetKeyboardLayoutList(nBuff As Integer, <Out> lpList As IntPtr()) As UInteger
End Function
|
|
Примерный вариант использования
vb.net |
1
2
3
4
| Dim len = GetKeyboardLayoutList(0, Nothing)
Dim lll(len - 1) As IntPtr
GetKeyboardLayoutList(len, lll)
Array.ForEach(lll, AddressOf Console.WriteLine)
|
|
PS
Функции, определенные с помощью атрибута DllImport (а не с помощью Declare) должны быть статическими, следовательно в таком виде, как они представлены здесь из можно использовать только в модуле. В классе они должны объявляться как Shared.
Вот собственно и все.
Комментариев нет :
Отправить комментарий