在 WPF C# 中获取键盘布局

Posted

技术标签:

【中文标题】在 WPF C# 中获取键盘布局【英文标题】:Getting the Keyboard Layout in WPF C# 【发布时间】:2019-05-28 01:01:44 【问题描述】:

我需要为我的 WPF 应用程序获取当前 Windows 键盘布局,以正确映射每个键并处理 AZERTY 以及 QWERTY 和 QWERTZ(等等......)

我注意到一个问题,因为我使用的是法语布局 (azerty),但我的窗口显示为英文。

我尝试了各种方法来正确获取布局但没有结果:

var test1 = InputLanguageManager.Current.CurrentInputLanguage;

var test2 = CultureInfo.CurrentCulture;

我尝试让 ENG 语言与 AZERTY 布局、ENG 语言与 QWERTY 布局和 FRA 语言与 AZERTY 布局,但我的测试输出总是不同。我可以正确显示语言(en-GB),但不能正确显示布局。

【问题讨论】:

Dupish ...detect-current-keyboard-language-layout-name-in-multi-language-computer - 但它使用与您相同的方式...所以可能不是解决方案? 我也试过了...var HKL = NativeMethods.GetCurrentKeyboardLayout(); `var aszasza = Thread.CurrentThread.CurrentCulture; `var nfaoiure = new CultureInfo("fr"); `var oui = CultureInfo.CurrentCulture; `var frefrefre = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; `var formLang = InputLanguageManager.Current.CurrentInputLanguage; 【参考方案1】:

以下 PowerShell1 脚本声明了 Get-KeyboardLayoutForPid 函数,该函数可靠地获取任何进程的当前 Windows 键盘布局2

if ( $null -eq ('Win32Functions.KeyboardLayout' -as [type]) ) 
    Add-Type -MemberDefinition @'
        [DllImport("user32.dll")] 
        public static extern IntPtr GetKeyboardLayout(uint thread);
'@ -Name KeyboardLayout -Namespace Win32Functions


Function Get-KeyboardLayoutForPid 
    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$False, ValueFromPipeline=$False)]
        [int]$Id = $PID,
        # used formerly for debugging
        [parameter(Mandatory=$False, DontShow=$True)]
        [switch]$Raw
    )

    $InstalledInputLanguages = [System.Windows.Forms.InputLanguage]::InstalledInputLanguages
    $CurrentInputLanguage    = [System.Windows.Forms.InputLanguage]::DefaultInputLanguage # CurrentInputLanguage
    $CurrentInputLanguageHKL = $CurrentInputLanguage.Handle # just an assumption
    ### Write-Verbose ('CurrentInputLanguage: 0, 0x1:X8 (2), 3' -f $CurrentInputLanguage.Culture, ($CurrentInputLanguageHKL -band 0xffffffff), $CurrentInputLanguageHKL, $CurrentInputLanguage.LayoutName)

    Function GetRealLayoutName ( [IntPtr]$HKL ) 
    $regBase = 'Registry::' + 
                'HKLM\SYSTEM\CurrentControlSet\Control\Keyboard Layouts'
    $LayoutHex = '0:x8' -f ($hkl -band 0xFFFFFFFF)
    if ( ($hkl -band 0xFFFFFFFF) -lt 0 ) 
        $auxKeyb = Get-ChildItem -Path $regBase | 
            Where-Object  
            $_.Property -contains 'Layout Id' -and 
                (Get-ItemPropertyValue -Path "Registry::$($_.Name)" `
                    -Name 'Layout Id' `
                    -ErrorAction SilentlyContinue
                ) -eq $LayoutHex.Substring(2,2).PadLeft(4,'0')
         | Select-Object -ExpandProperty PSChildName
     else 
        $auxKeyb = $LayoutHex.Substring(0,4).PadLeft(8,'0')
    
    $KbdLayoutName  = Get-ItemPropertyValue -Path (
        Join-Path -Path $regBase -ChildPath $auxKeyb
    ) -ErrorAction SilentlyContinue -Name 'Layout Text'
    $KbdLayoutName
    # Another option: grab localized string from 'Layout Display Name'
     # Function GetRealLayoutName

    Function  GetKbdLayoutForPid 
    Param (
        [parameter(Mandatory=$True, ValueFromPipeline=$False)]
        [int]$Id,
        [parameter(Mandatory=$False, DontShow=$True)]
        [string]$Parent = ''
    ) 
    $Processes = Get-Process -Id $Id
    $Weirds  = @('powershell_ise','explorer') # not implemented yet

    $allLayouts = foreach ( $Proces in $Processes ) 
        $LayoutsExtra = [ordered]@
        $auxKLIDs = @( for ( $i=0; $i -lt $Proces.Threads.Count; $i++ ) 
            $thread = $Proces.Threads[$i]
            ## The return value is the input locale identifier for the thread:
            $LayoutInt = [Win32Functions.KeyboardLayout]::GetKeyboardLayout( $thread.Id )
            $LayoutsExtra[$LayoutInt] = $thread.Id
        )
        Write-Verbose ('0,6 (1,6) 2: 3' -f $Proces.Id, $Parent, 
            $Proces.ProcessName, (($LayoutsExtra.Keys | 
                Select-Object -Property @ N='Handl';E=('0:x8' -f ($_ -band 0xffffffff)) | 
                Select-Object -ExpandProperty Handl) -join ', '))
        foreach ( $auxHandle in $LayoutsExtra.Keys ) 
            $InstalledInputLanguages | Where-Object  $_.Handle -eq $auxHandle 
        
        $ConHost = Get-WmiObject Win32_Process -Filter "Name = 'conhost.exe'"
        $isConsoleApp = $ConHost | Where-Object  $_.ParentProcessId -eq $Proces.Id 
        if ( $null -ne $isConsoleApp ) 
            GetKbdLayoutForPid -Id ($isConsoleApp.ProcessId) -Parent ($Proces.Id -as [string])
        
    
    if ( $null -eq $allLayouts ) 
        # Write-Verbose ('0,6 (1,6) 2: 3' -f $Proces.Id, $Parent, $Proces.ProcessName, '')
     else 
        $allLayouts
    
     # GetKbdLayoutForPid

    $allLayoutsRaw = GetKbdLayoutForPid -Id $Id
    if ( $null -ne $allLayoutsRaw ) 
        if ( ([bool]$PSBoundParameters['Raw']) ) 
            $allLayoutsRaw
         else 
            $retLayouts = $allLayoutsRaw | 
                Sort-Object -Property Handle -Unique | 
                Where-Object  $_.Handle -ne $CurrentInputLanguageHKL 
            if ( $null -eq $retLayouts )  $retLayouts = $CurrentInputLanguage 
            $RealLayoutName = $retLayouts.Handle | 
                ForEach-Object  GetRealLayoutName -HKL $_ 
            $ProcessAux = Get-Process -Id $Id
            $retLayouts | Add-Member -MemberType NoteProperty -Name 'ProcessId' -Value "$Id"
            $retLayouts | Add-Member -MemberType NoteProperty -Name 'ProcessName' -Value ($ProcessAux | Select-Object -ExpandProperty ProcessName )
            # $retLayouts | Add-Member -MemberType NoteProperty -Name 'WindowTitle' -Value ($ProcessAux | Select-Object -ExpandProperty MainWindowTitle )
            $retLayouts | Add-Member -MemberType NoteProperty -Name 'RealLayoutName' -Value ($RealLayoutName -join ';')
            $retLayouts
        
    
<#

.Synopsis
Get the current Windows Keyboard Layout for a process.

.Description
Gets the current Windows Keyboard Layout for a process. Identify the process
using -Id parameter.

.Parameter Id
A process Id, e.g.
- Id property of System.Diagnostics.Process instance (Get-Process), or
- ProcessId property (an instance of the Win32_Process WMI class), or 
- PID property from "TaskList.exe /FO CSV", …

.Parameter Raw
Parameter -Raw is used merely for debugging. 

.Example
Get-KeyboardLayoutForPid

This example shows output for the current process (-Id $PID). 
Note that properties RealLayoutName and LayoutName could differ (the latter is wrong; a bug in [System.Windows.Forms.InputLanguage] implementation?)

ProcessId      : 2528
ProcessName    : powershell
RealLayoutName : United States-International
Culture        : cs-CZ
Handle         : -268368891
LayoutName     : US

.Example
. D:\PShell\tests\Get-KeyboardLayoutForPid.ps1  # activate the function

Get-Process -Name * | 
    ForEach-Object  Get-KeyboardLayoutForPid -Id $_.Id -Verbose 

This example shows output for each currently running process, unfortunately
even (likely unusable) info about utility/service processes. 

The output itself can be empty for most processes, but the verbose stream
shows (hopefully worthwhile) info where current keboard layout is held.

Note different placement of the current keboard layout ID:
- console application      (cmd, powershell, ubuntu): conhost
- combined GUI/console app (powershell_ise)         : the app itself
- classic  GUI apps        (notepad, notepad++, …)  : the app itself
- advanced GUI apps        (iexplore)               : Id ≘ tab
- "modern" GUI apps        (MicrosoftEdge*)         : Id ≟ tab (unclear)
- combined GUI/service app (explorer)               : indiscernible
- etc…                     (this list is incomplete).

For instance, iexplore.exe creates a separate process for each open window 
or tab, so their identifying and assigning input languages is an easy task.

On the other side, explorer.exe creates the only process, regardless of 
open visible window(s), so they are indistinguishable by techniques used here…

.Example
gps -Name explorer | %  Get-KeyboardLayoutForPid -Id $_.Id  | ft -au

This example shows where the function could fail in a language multifarious environment:

ProcessId ProcessName RealLayoutName Culture     Handle LayoutName 
--------- ----------- -------------- -------     ------ ---------- 
5344      explorer    Greek (220);US el-GR   -266992632 Greek (220)
5344      explorer    Greek (220);US cs-CZ     67699717 US         

- scenario: 
  open three different file explorer windows and set their input languages
  as follows (their order does not matter):
    - 1st window: let default input language   (e.g. Czech, in my case),
    - 2nd window: set different input language (e.g. US English),
    - 3rd window: set different input language (e.g. Greek).
- output:
  an array (and note that default input language window isn't listed).

.Inputs
No object can be piped to the function. Use -Id pameter instead,
named or positional.

.Outputs
[System.Windows.Forms.InputLanguage] extended as follows:
                                     note the <…> placeholder

Get-KeyboardLayoutForPid | Get-Member -MemberType Properties

   TypeName: System.Windows.Forms.InputLanguage

Name           MemberType   Definition
----           ----------   ----------
ProcessId      NoteProperty string ProcessId=<…>
ProcessName    NoteProperty System.String ProcessName=powershell
RealLayoutName NoteProperty string RealLayoutName=<…>
Culture        Property     cultureinfo Culture get;
Handle         Property     System.IntPtr Handle get;
LayoutName     Property     string LayoutName get;

.Notes
To add the `Get-KeyboardLayoutForPid` function to the current scope,
run the script using `.` dot sourcing operator, e.g. as 
. D:\PShell\tests\Get-KeyboardLayoutForPid.ps1

Auhor:     https://***.com/users/3439404/josefz
Created:   2019-11-24
Revisions:  

.Link

.Component
P/Invoke

<##>

 # Function Get-KeyboardLayoutForPid

if ( -not ('System.Windows.Forms.InputLanguage' -as [type]) ) 
    Add-Type -AssemblyName System.Windows.Forms

Get-KeyboardLayoutForPid 函数包含一个位于其主体末尾的基于注释的帮助。 希望它的原理在C#easy中可以实现。

我的方法的主要思想:

    假设给定进程的当前键盘布局 (CurrentInputLanguage) 是(取决于用户的)默认键盘布局 (DefaultInputLanguage em>)。 收集与给定进程的每个线程关联的键盘布局 (allLayoutsRaw)。请注意控制台应用程序的这个技巧:收集与子 conhost 进程的每个线程相关联的键盘布局。 如果 allLayoutsRaw 集合中的键盘布局与 DefaultInputLanguage 不同,那么它就是受欢迎的键盘布局 (retLayouts em>)。

1 在 PowerShell Core (pwsh.exe) 中不起作用。

2 在多语言环境中explorer 进程可能会失败,请参阅基于注释 帮助中的失败场景示例。

【讨论】:

【参考方案2】:

我不确定这个问题 - 您是想知道当前的键盘布局还是要设置键盘布局。

在这两种情况下,InputLanguageManager 都应该有所帮助。

您可以尝试将输入语言管理器设置为适当的cultureInfo 对象。 这应该会更改 WPF 应用程序的键盘布局

InputLanguageManager.Current.CurrentInputLanguage = new CultureInfo("fr-FR");

【讨论】:

我想获取用户的当前布局,我使用自己的布局进行测试,因为我的布局与我的语言偏好不匹配。当我使用计算机时,语言设置为 en-GB,但键盘布局是 AZERTY。 InputLanguageManager获取的不是布局而是语言(除非我错了)

以上是关于在 WPF C# 中获取键盘布局的主要内容,如果未能解决你的问题,请参考以下文章

如何在不按 Enter C# WPF 的情况下检测我的键盘

如何使用 C# 更改单个键盘布局

WPF 禁用TextBox的触摸后自动弹出虚拟键盘

WPF 禁用TextBox的触摸后自动弹出虚拟键盘

如何从 WPF 中的子控件中获取键盘?

使用 .NET 4.5.2 从 C# 代码更改键盘布局