在WPF C#中获取键盘布局 [英] Getting the Keyboard Layout in WPF C#

查看:112
本文介绍了在WPF C#中获取键盘布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need to get the Current Windows Keyboard Layout for my WPF application to map each key correctly and handle AZERTY as well as QWERTY and QWERTZ (and so on...)

由于我使用的是法式版式(azerty),但是我的窗口以英文显示,所以我发现了一个问题.

I noticed a problem since I am working with a French layout (azerty) but my windows is displayed in English.

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

I tried various methods to get the layout correctly but without results :

var test1 = InputLanguageManager.Current.CurrentInputLanguage;

var test2 = CultureInfo.CurrentCulture;

我尝试使用具有AZERTY布局的ENG语言,具有QWERTY布局的ENG语言和具有AZERTY布局的FRA语言,但是我的测试输出始终是不同的.我可以正确显示语言(en-GB),但不能正确显示布局.

I tried by having ENG language with AZERTY layout, ENG language with QWERTY layout and FRA language with AZERTY layout but the output from my tests were always different. I could get the language displayed correctly (en-GB) but not the layout.

推荐答案

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

The following PowerShell1 script declares Get-KeyboardLayoutForPid function which reliably gets the Current Windows Keyboard Layout for any process2.

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}, 0x{1: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://stackoverflow.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中实现.

The Get-KeyboardLayoutForPid function contains a Comment-Based Help placed at the end of its body.
I hope that its principle is implementable in C# easy.

我的方法的主要思想:

  1. 假定给定进程的当前键盘布局( CurrentInputLanguage )是(用户相关的)默认默认键盘布局( DefaultInputLanguage ).
  2. 收集与给定进程( allLayoutsRaw )的每个线程关联的键盘布局.请注意控制台应用程序的这一技巧:还要收集与子conhost进程的每个线程相关的键盘布局.
  3. 如果 allLayoutsRaw 集合中的键盘布局与 DefaultInputLanguage 不同,则它是最受欢迎的键盘布局( retLayouts ).
  1. Suppose that the current keyboard layout (CurrentInputLanguage) for a given process is the (user-dependant) default one (DefaultInputLanguage).
  2. Collect keyboard layouts associated with every thread of given process (allLayoutsRaw). Note this trick for a console application: collect keyboard layouts associated with every thread of child conhost process as well.
  3. If there is a keyboard layout different from DefaultInputLanguage in the allLayoutsRaw collection then it's the sought-after one (retLayouts).


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


1 Does not work in PowerShell Core (pwsh.exe).

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

2 Might fail for explorer process in a language multifarious environment, see an example of failing scenario in the Comment-Based help.

这篇关于在WPF C#中获取键盘布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆