如何获取当前所选键盘布局的显示名称

问题描述 投票:0回答:3

我需要按照语言栏显示输入语言的方式向用户显示输入语言列表。

例如:

目前我有

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

c# winapi
3个回答
4
投票

如果 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();
    }
}

2
投票

这是

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"


0
投票

我终于找到了如何获取当前语言。 它需要 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 返回相同的名称?

© www.soinside.com 2019 - 2024. All rights reserved.