我需要按照语言栏显示输入语言的方式向用户显示输入语言列表。
例如:
目前我有
class Program
{
static void Main(string[] args)
{
var langs = InputLanguage.InstalledInputLanguages;
foreach (InputLanguage lang in langs)
{
Console.WriteLine(lang.LayoutName);
}
}
}
这将打印以下内容
US
Bulgarian
Bulgarian
如你所见,我们无法区分这两个“保加利亚人” 哪一个是 BGPT,哪一个只是 BG。
Culture
两者也相同。
唯一的区别是 lang.Handle
是 IntPtr
类型。
我想我必须使用句柄 P/Invoke 一些 win32 API 才能获取该显示名称。
有什么想法吗?
编辑:
在 powershell 中执行
Get-WinUserLanguageList
显示以下内容
LanguageTag : en-US
Autonym : English (United States)
EnglishName : English
LocalizedName : English (United States)
ScriptName : Latin
InputMethodTips : {0409:00000409}
Spellchecking : True
Handwriting : False
LanguageTag : bg
Autonym : български
EnglishName : Bulgarian
LocalizedName : Bulgarian
ScriptName : Cyrillic
InputMethodTips : {0402:00040402, 0402:00030402}
Spellchecking : True
Handwriting : False
输入法提示是这里的关键。
0402:00040402 是 BG,0402:00030402 是 BGPT
如果 PowerShell 有您正在寻找的东西,您总是可以从那里获取它。添加对 C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell .0\System.Management.Automation.dll 和 C:\Windows\Microsoft.NET ssembly\GAC_MSIL\Microsoft.InternationalSettings.Commands 4.0_3 的引用.0.0.0__31bf3856ad364e35\Microsoft.InternationalSettings.Commands.dll
class Program
{
static void Main(string[] args)
{
System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create();
List<Microsoft.InternationalSettings.Commands.WinUserLanguage> userLangList = ps.AddCommand("Get-WinUserLanguageList").Invoke()[0].BaseObject as List<Microsoft.InternationalSettings.Commands.WinUserLanguage>;
foreach (Microsoft.InternationalSettings.Commands.WinUserLanguage userLang in userLangList)
{
Console.WriteLine("{0,-31}{1,-47}", "Antonym", userLang.Autonym);
Console.WriteLine("{0,-31}{1,-47}", "EnglishName", userLang.EnglishName);
Console.WriteLine("{0,-31}{1,-47}", "Handwriting", userLang.Handwriting);
Console.WriteLine("{0,-31}{1,-47}", "InputMethodTips", String.Join(",", userLang.InputMethodTips));
Console.WriteLine("{0,-31}{1,-47}", "LanguageTag", userLang.LanguageTag);
Console.WriteLine("{0,-31}{1,-47}", "LocalizedName", userLang.LocalizedName);
Console.WriteLine("{0,-31}{1,-47}", "ScriptName", userLang.ScriptName);
Console.WriteLine("{0,-31}{1,-47}", "Spellchecking", userLang.Spellchecking);
Console.WriteLine();
}
}
}
或者,Microsoft 从注册表中获取大部分此类信息,您也可以这样做:
class Program
{
static void Main(string[] args)
{
(new System.Security.Permissions.RegistryPermission(System.Security.Permissions.PermissionState.Unrestricted)).Assert();
Microsoft.Win32.RegistryKey rkLanguages = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Control Panel\\International\\User Profile");
foreach (string str in rkLanguages.GetSubKeyNames())
{
Console.WriteLine(str);
Microsoft.Win32.RegistryKey rkLang = rkLanguages.OpenSubKey(str);
foreach (string value in rkLang.GetValueNames())
{
if (rkLang.GetValueKind(value) == Microsoft.Win32.RegistryValueKind.DWord)
{
string blah = String.Concat("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\", value.Split(new char[] { ':' })[1]);
Microsoft.Win32.RegistryKey rkKeyboardLayout = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(blah);
Console.WriteLine(rkKeyboardLayout.GetValue("Layout Text"));
}
}
Console.WriteLine();
}
System.Security.CodeAccessPermission.RevertAssert();
}
}
这是
InputLanguage.LayoutName
实现中的错误,已修复:https://github.com/dotnet/winforms/pull/8439
你可以从固定代码中得到这个想法:
private static string KeyboardLayoutsRegistryPath => @"SYSTEM\CurrentControlSet\Control\Keyboard Layouts";
public string LayoutName
{
get
{
// There is no good way to do this in Windows. GetKeyboardLayoutName does what we want, but only for the
// current input language; setting and resetting the current input language would generate spurious
// InputLanguageChanged events.
// Try to extract needed information manually.
string layoutName = GetKeyboardLayoutNameForHKL(_handle);
// https://learn.microsoft.com/windows/win32/intl/using-registry-string-redirection#create-resources-for-keyboard-layout-strings
using RegistryKey? key = Registry.LocalMachine.OpenSubKey($@"{KeyboardLayoutsRegistryPath}\{layoutName}");
if (key is not null)
{
// Localizable string resource associated with the keyboard layout
if (key.GetValue("Layout Display Name") is string layoutDisplayName &&
SHLoadIndirectString(ref layoutDisplayName))
{
return layoutDisplayName;
}
// Fallback to human-readable name for backward compatibility
if (key.GetValue("Layout Text") is string layoutText)
{
return layoutText;
}
}
return SR.UnknownInputLanguageLayout;
}
}
internal static string GetKeyboardLayoutNameForHKL(IntPtr hkl)
{
// According to the GetKeyboardLayout API function docs low word of HKL contains input language.
int language = PARAM.LOWORD(hkl);
// High word of HKL contains a device handle to the physical layout of the keyboard but exact format of this
// handle is not documented. For older keyboard layouts device handle seems contains keyboard layout
// language which we can use as KLID.
int device = PARAM.HIWORD(hkl);
// But for newer keyboard layouts device handle contains layout id if its high nibble is 0xF. This id may be
// used to search for keyboard layout under registry.
// NOTE: this logic may break in future versions of Windows since it is not documented.
if ((device & 0xF000) == 0xF000)
{
// Extract layout id from the device handle
int layoutId = device & 0x0FFF;
using RegistryKey? key = Registry.LocalMachine.OpenSubKey(KeyboardLayoutsRegistryPath);
if (key is not null)
{
// Match keyboard layout by layout id
foreach (string subKeyName in key.GetSubKeyNames())
{
using RegistryKey? subKey = key.OpenSubKey(subKeyName);
if (subKey is null)
{
continue;
}
if (subKey.GetValue("Layout Id") is not string subKeyLayoutId)
{
continue;
}
if (layoutId == Convert.ToInt32(subKeyLayoutId, 16))
{
Debug.Assert(subKeyName.Length == 8, $"unexpected key length in registry: {subKey.Name}");
return subKeyName;
}
}
}
}
else
{
// Keyboard layout language overrides input language, if available. This is crucial in cases when
// keyboard is installed more than once or under different languages. For example when French keyboard
// is installed under US input language we need to return French keyboard name.
if (device != 0)
{
language = device;
}
}
return language.ToString("x8");
}
必须将其组合成一根字符串之后
$"{InputLanguage.Culture.DisplayName} - {InputLanguage.LayoutName} keyboard"
我终于找到了如何获取当前语言。 它需要 user32.dll 中的以下函数
static extern bool GetKeyboardLayoutName([Out] StringBuilder pwszKLID);
static extern IntPtr GetKeyboardLayout(uint idThread);
private static extern IntPtr GetForegroundWindow();
static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
public static extern int ActivateKeyboardLayout(int HKL, int flags);
IntPtr layout = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero));
ActivateKeyboardLayout((int)layout, 100);
GetKeyboardLayoutName(Input);
return Input.toString
您会得到类似
00000409
的语言代码,代表 en-US
它适用于语言切换。
我在这里创建了这个例子 https://github.com/Roman-Tarasiuk/WindowsTools/blob/master/WindowsTools.Infrastruct/KeyboardLayout.cs
powershell 脚本看起来如何:
add-type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace WindowsTools.Infrastructure
{
public class KeyboardLayout
{
static StringBuilder Input = new StringBuilder(9);
[DllImport("user32.dll")]
static extern bool GetKeyboardLayoutName([Out] StringBuilder pwszKLID);
[DllImport("user32.dll")]
static extern IntPtr GetKeyboardLayout(uint idThread);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
[DllImport("user32.dll")]
public static extern int ActivateKeyboardLayout(int HKL, int flags);
public static StringBuilder GetInput()
{
IntPtr layout = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero));
ActivateKeyboardLayout((int)layout, 100);
GetKeyboardLayoutName(Input);
return Input;
}
//
public static string GetLayout()
{
var result = GetInput();
return MapLayoutName(result.ToString());
}
public static String MapLayoutName(string code = null)
{
if (code == null)
return "null";
switch (code)
{
case "00000409":
return "ENG";
case "00000419":
return "RU";
default:
return "unknown";
}
}
}
}
"@
Function Get-CurrentInputLanguage {
$layout_name = [WindowsTools.Infrastructure.KeyboardLayout]::GetLayout()
Write-Output $layout_name
}
Function SwitchLang {
$l1, $l2 = GetLangs
Set-WinUserLanguageList -LanguageList $l2, $l1 -force -WarningAction:SilentlyContinue
}
Function SwitchLang {
Import-Module "C:\tools\autoit-v3\AutoItX\AutoItX.psd1"
Initialize-AU3
#default start+space hotkeys to switch language, requires to install zip archive https://www.autoitscript.com/site/autoit/downloads/
Send-AU3Key -Key "{LWINDOWN} {LWINUP}"
}
function SwitchLangTo {
param($lang)
$cur_lang = Get-CurrentInputLanguage
if ( $lang -ne $cur_lang ) {
SwitchLang
}
}
Get-CurrentInputLanguage
# SwitchLang
# SwitchLangTo 'ENG'
# SwitchLangTo 'RU'
另一种方法是处理 winforms 应用程序的事件
WM_INPUTLANGCHANGE
为什么布局更改后 GetKeyboardLayoutName 返回相同的名称?