Получить список персонажей, которые "принадлежат" к текущей культуре
Я хотел бы создать строку, содержащую все символы, которые текущий пользователь сможет вводить на клавиатуре. Для пользователя в англоязычной культуре это будет 26 прописных и 26 строчных букв, 10 десятичных цифр и более 30 символов. Пользователи из других стран будут иметь несколько разных персонажей.
Я понимаю, что у пользователя есть способы ввода символов, которых нет на его клавиатуре, но я не могу предположить, что они знают, как это сделать. Я ищу набор персонажей, для которых было бы разумно предположить, что любой с текущей культурой должен иметь возможность войти. Это не конец света, если в наборе пропущен один или два специальных символа, но я бы хотел, чтобы он был достаточно завершенным.
Я могу быстро получить набор символов английского языка США, либо жестко запрограммировав его, либо используя такую функцию:
Function GetCharacterSet() As String
Return Enumerable.Range(32, 95).Select(Function(i) Chr(i)).ToArray
End Function
Я не уверен, как сделать это надежно для других культур. Я могу закодировать функцию следующим образом:
Function GetCharacterSet() As String
Dim chars As New List(Of Char)
For i As Integer = 0 To UInt16.MaxValue
Dim ch As Char = ChrW(i)
If Char.IsLetterOrDigit(ch) OrElse Char.IsPunctuation(ch) OrElse ch = " "c Then
chars.Add(ch)
End If
Next
Return chars.ToArray
End Function
Но получающаяся (очень длинная) строка содержит символы, допустимые в любой культуре. Есть ли способ проверить, является ли символ буквой, цифрой или пунктуацией только в текущей культуре?
1 ответ
Ладно, это немного задом наперед, но пока я смог справиться с API-интерфейсами раскладки клавиатуры:
public class Api
{
[DllImport("kernel32.dll")]
public static extern uint GetCurrentThreadId();
[DllImport("user32.dll")]
public static extern IntPtr GetKeyboardLayout(uint idThread);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern short VkKeyScanEx(char ch, IntPtr dwhkl);
}
class Program
{
static bool IsRepresentable(char c, IntPtr keyboardLayout)
{
var x = Api.VkKeyScanEx(c, keyboardLayout);
return x != -1;
}
static IEnumerable<char> GetKeyboardLayoutCharacters(IntPtr keyboardLayout)
{
return
Enumerable.Range(32, char.MaxValue - 32)
.Select(n => (char)n)
.Where(c => IsRepresentable(c, keyboardLayout));
}
static void Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
var layout = Api.GetKeyboardLayout(Api.GetCurrentThreadId());
Console.WriteLine(string.Concat(GetKeyboardLayoutCharacters(layout)));
}
}
Это фактически ищет весь BMP и спрашивает, может ли каждый символ быть представлен данной раскладкой клавиатуры. Не идеально, но возвращает следующее:
Немецкий:
!"#$%&'()*+,-./0123456789:;<=>?@
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`
abcdefghijklmnopqrstuvwxyz
{|}~§°²³´µÄÖÜßäöüẞ€
Польский:
!"#$%&'()*+,-./0123456789:;<=>?@
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`
abcdefghijklmnopqrstuvwxyz
{|}~ÓóĄąĆćĘꣳŃńŚśŹźŻż€
Американский английский:
!"#$%&'()*+,-./0123456789:;<=>?@
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`
abcdefghijklmnopqrstuvwxyz{|}~
США Интернешнл;-):
!"#$%&'()*+,-./0123456789:;<=>?@
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`
abcdefghijklmnopqrstuvwxyz
{|}~¡¢£¤¥¦§¨©«¬®°²³´µ¶¹»¼½¾¿ÁÄÅÆÇÉÍÐÑÓÖ×ØÚÜÞßáäåæçéíðñóö÷øúüþ‘’€
Я вполне уверен, что должен быть способ на самом деле захватить символы, которые может произвести данная раскладка клавиатуры, потому что в приведенном выше списке не учитываются мертвые клавиши (например, US International может на самом деле производить ÿ
, õ
, или же ï
но их нет в списке, потому что их невозможно создать с помощью Shift, Ctrl или Alt - вы должны использовать мертвые клавиши). Но в первом приближении это может быть уже полезно. Кроме того, есть несколько странных вещей, не столько включение U+007F (что является Del), но скорее U+F000 и U+F001. Может потребоваться дополнительная фильтрация возвращаемого списка.
Этот метод также предполагает, что раскладка клавиатуры активна для пользователя, который представляет его язык. Тем не менее, это, вероятно, наиболее часто, если речь идет только о языке текущего пользователя.
РЕДАКТИРОВАТЬ:
Версия Vb.Net
Private NotInheritable Class NativeMethods
<DllImport("user32.dll", CharSet:=CharSet.Unicode)>
Public Shared Function VkKeyScanEx(ByVal ch As Char, ByVal dwhkl As IntPtr) As Short
End Function
End Class
<Extension>
Public Function IsAlphabetic(ByVal sender As String,
ByVal culture As CultureInfo) As Boolean
If Not CultureInfo.GetCultures(CultureTypes.InstalledWin32Cultures).Contains(culture) Then
Throw New CultureNotFoundException(paramName:="culture", message:="Culture not installed.")
Else
' Keyboard Layout Handle (HKL)
Dim hkl As IntPtr = InputLanguage.FromCulture(culture).Handle
Dim charList As New List(Of Char)
For index As UShort = 0US To (UShort.MaxValue - 1US)
Dim c As Char = Convert.ToChar(index)
' The check for being a letter can always be removed if symbols or numbers should be allowed, too.
If (NativeMethods.VkKeyScanEx(c, hkl) <> -1S) AndAlso Char.IsLetterOrDigit(c) Then
charList.Add(c)
End If
Next index
For Each c As Char In sender
If Not charList.Contains(c) Then
Return False
End If
Next
Return True
End If
End Function